2Contains the GameSummaryPlotter abstract base class, and
3multiple subclasses that implement it.
5Each sub-class plots a different portion of data from a GameSummary.
9from abc
import ABC, abstractmethod
10from typing
import Dict, List, Tuple, cast
14import xiangqi_bindings
as bindings
15from matplotlib
import pyplot
as plt
22 Abstract base class for plotting data stored in pandas
23 dataframes (one df
for each player) to a numpy array of matplotlib Axes
26 evaluating_player_line_colors = {
27 bindings.PieceColor.kRed: "red",
28 bindings.PieceColor.kBlk:
"black",
31 player_text_labels = {
32 bindings.PieceColor.kRed:
"Red Player",
33 bindings.PieceColor.kBlk:
"Black Player",
36 non_evaluating_player_line_colors = {
37 bindings.PieceColor.kRed:
"lightcoral",
38 bindings.PieceColor.kBlk:
"darkgray",
44 y_labels: Tuple[str, ...],
45 log_scale_rows: int | Tuple[int, ...] = tuple(),
46 red_data: pd.DataFrame =
None,
47 black_data: pd.DataFrame =
None,
48 add_plot_column_titles: bool =
True,
52 if type(log_scale_rows) == int:
53 log_scale_rows = (log_scale_rows,)
63 def dfs(self) -> Dict[str, pd.DataFrame]:
65 bindings.PieceColor.kRed.name: self.
_red_data,
69 def has_data(self, player: bindings.PieceColor) -> bool:
70 return self.
dfs[player.name]
is not None
76 int(self.
has_data(bindings.PieceColor.kRed)),
77 int(self.
has_data(bindings.PieceColor.kBlk)),
84 self.
dfs[bindings.PieceColor.kRed.name].columns
85 == self.
dfs[bindings.PieceColor.kBlk.name].columns
89 for grid_row_idx, grid_row
in enumerate(
93 ax = cast(plt.Axes, ax)
96 for grid_row_idx, ax
in enumerate(self.
axes[:, 0]):
97 ax = cast(plt.Axes, ax)
98 ax.set_ylabel(self.
y_labels[grid_row_idx], fontsize=14)
99 ax.yaxis.set_label_coords(-0.15, 0.5)
102 for grid_row_idx, grid_row
in enumerate(self.
axes[0:-1, :]):
104 ax = cast(plt.Axes, ax)
105 ax.set_xticklabels([])
107 for grid_col_idx, ax
in enumerate(self.
axes[-1, :]):
108 ax = cast(plt.Axes, ax)
109 ax.set_xlabel(
"Game Move Number", fontsize=14)
114 return {bindings.PieceColor.kRed: 0, bindings.PieceColor.kBlk: 1}
115 elif self.
has_data(player=bindings.PieceColor.kRed):
117 bindings.PieceColor.kRed: 0,
118 bindings.PieceColor.kBlk:
None,
120 elif self.
has_data(player=bindings.PieceColor.kBlk):
122 bindings.PieceColor.kRed:
None,
123 bindings.PieceColor.kBlk: 0,
129 item.columns
for item
in self.
dfs.values()
if item
is not None
136 for col_idx
in range(axes_row.shape[0]):
137 cur_ax = cast(plt.Axes, axes_row[col_idx])
138 y_limits_low.append(cur_ax.get_ylim()[0])
139 y_limits_high.append(cur_ax.get_ylim()[1])
140 for col_idx
in range(axes_row.shape[0]):
141 cur_ax = cast(plt.Axes, axes_row[col_idx])
142 cur_ax.set_ylim((min(y_limits_low), max(y_limits_high)))
145 for row_idx
in range(self.
axes.shape[0]):
153 if self.axes.shape[1] == 2:
154 for idx, ax
in enumerate(self.axes[:, 1]):
155 ax = cast(plt.Axes, ax)
156 ax.set_yticklabels([])
159 for idx, ax
in enumerate(self.
axes[0, :]):
160 ax = cast(plt.Axes, ax)
182 Implements GameSummaryPlotter, and produces stacked plots of Minimax
183 search result counts grouped by MinimaxResultType.
189 y_labels: Tuple[str] = (
191 "Node Counts Log Scale",
193 log_scale_rows: int | Tuple[int, int] = 1,
194 cmap_name: str =
"Accent",
195 red_data: pd.DataFrame =
None,
196 black_data: pd.DataFrame =
None,
197 add_plot_column_titles: bool =
True,
202 log_scale_rows=log_scale_rows,
204 black_data=black_data,
205 add_plot_column_titles=add_plot_column_titles,
207 self.
cmap = plt.get_cmap(cmap_name)
212 ) -> Dict[str, Tuple[float, float, float, float]]:
214 cast(str, col_name): self.
cmap(idx)
219 self, player: bindings.PieceColor, ax: plt.Axes
221 df = self.
dfs[player.name]
222 df_sorted = df[sorted(df.columns, key=
lambda col: df[col].mean())]
227 with warnings.catch_warnings():
228 warnings.filterwarnings(
230 category=UserWarning,
231 message=
"Data has no positive values, and therefore cannot be log-scaled.",
236 labels=df_sorted.columns,
237 colors=sorted_colors,
241 for plot_row
in range(2):
243 ax = cast(plt.Axes, self.
axes[plot_row, plot_col])
247 upper_right_plot = cast(plt.Axes, self.
axes[0, -1])
257 upper_right_plot.legend(
260 bbox_to_anchor=(1.05, 0.50),
269 for player
in [bindings.PieceColor.kRed, bindings.PieceColor.kBlk]:
277 Implements GameSummaryPlotter, and produces plots showing time spent
278 by core MinimaxMoveEvaluator(s) evaluating nodes
and selecting Moves.
281 search_stats_time_cols = ["search_time_s",
"mean_time_per_node_ns"]
286 log_scale_rows: int | Tuple[int, ...] = tuple(),
287 red_data: pd.DataFrame =
None,
288 black_data: pd.DataFrame =
None,
289 add_plot_column_titles: bool =
True,
295 "Time per Node (ns)",
297 log_scale_rows=log_scale_rows,
299 black_data=black_data,
300 add_plot_column_titles=add_plot_column_titles,
304 df = self.
dfs[player.name]
307 ax = cast(plt.Axes, self.
axes[data_col_idx, plot_col])
315 for player
in [bindings.PieceColor.kRed, bindings.PieceColor.kBlk]:
322 Implements GameSummaryPlotter, and plots evaluated score of each move of
323 each Player using a Minimax algorithm
in a Game.
329 log_scale_rows: int | Tuple[int, ...] = tuple(),
330 red_data: pd.DataFrame =
None,
331 black_data: pd.DataFrame =
None,
332 add_plot_column_titles: bool =
True,
336 y_labels=(
"Evaluation Score",),
337 log_scale_rows=log_scale_rows,
339 black_data=black_data,
340 add_plot_column_titles=add_plot_column_titles,
345 largest_score_magnitudes = []
346 inf_like_values = [np.iinfo(PointsT).max, np.iinfo(PointsT).min]
347 for df
in self.
dfs.values():
349 assert df[
"eval_score"].dtype == PointsT
350 non_inf_like_values = df[
351 ~df[
"eval_score"].isin(inf_like_values)
353 largest_score_magnitudes.append(
354 np.abs(non_inf_like_values.min())
356 largest_score_magnitudes.append(
357 np.abs(non_inf_like_values.max())
359 return max(largest_score_magnitudes)
363 data: pd.Series, magnitude: int | float
365 assert magnitude >= 0
367 pd.Series, np.clip(data, a_min=-1 * magnitude, a_max=magnitude)
372 player: bindings.PieceColor,
374 is_evaluating_player: bool =
True,
377 df = self.
dfs[player.name]
378 if is_evaluating_player:
383 line_style =
"dotted"
387 data=df[
"eval_score"],
391 linestyle=line_style,
394 legend = ax.legend(loc=
"upper left", fontsize=
"12")
395 legend.get_frame().set_facecolor(
"white")
399 ax = cast(plt.Axes, self.
axes[0, plot_grid_col])
402 if self.
has_data(player=bindings.opponent_of(player)):
404 player=bindings.opponent_of(player),
406 is_evaluating_player=
False,
410 for player
in [bindings.PieceColor.kRed, bindings.PieceColor.kBlk]:
Implements GameSummaryPlotter, and plots evaluated score of each move of each Player using a Minimax ...
pd.Series symmetric_winsorize(pd.Series data, int|float magnitude)
def plot_player_and_opponent_overlay(self, bindings.PieceColor player)
def __init__(self, np.ndarray axes, int|Tuple[int,...] log_scale_rows=tuple(), pd.DataFrame red_data=None, pd.DataFrame black_data=None, bool add_plot_column_titles=True)
int largest_magnitude_noninf_eval_score(self)
def plot_player_data(self, bindings.PieceColor player, plt.Axes ax, bool is_evaluating_player=True)
Abstract base class for plotting data stored in pandas dataframes (one df for each player) to a numpy...
pd.Index data_columns(self)
int num_players_with_data(self)
def match_y_limits(np.ndarray axes_row)
def match_y_limits_all_rows(self)
def __init__(self, np.ndarray axes, Tuple[str,...] y_labels, int|Tuple[int,...] log_scale_rows=tuple(), pd.DataFrame red_data=None, pd.DataFrame black_data=None, bool add_plot_column_titles=True)
bool has_data(self, bindings.PieceColor player)
def remove_second_plot_col_ylabels(self)
Dict[bindings.PieceColor, int] player_plot_col(self)
dict non_evaluating_player_line_colors
dict evaluating_player_line_colors
def set_plot_col_titles(self)
Dict[str, pd.DataFrame] dfs(self)
Implements GameSummaryPlotter, and produces stacked plots of Minimax search result counts grouped by ...
def __init__(self, np.ndarray axes, Tuple[str] y_labels=("Node Counts", "Node Counts Log Scale",), int|Tuple[int, int] log_scale_rows=1, str cmap_name="Accent", pd.DataFrame red_data=None, pd.DataFrame black_data=None, bool add_plot_column_titles=True)
def plot_player_data(self, bindings.PieceColor player)
def plot_search_results_by_type_stacked(self, bindings.PieceColor player, plt.Axes ax)
Dict[str, Tuple[float, float, float, float]] data_column_colors(self)
Implements GameSummaryPlotter, and produces plots showing time spent by core MinimaxMoveEvaluator(s) ...
def plot_player_search_times(self, bindings.PieceColor player)
def __init__(self, np.ndarray axes, int|Tuple[int,...] log_scale_rows=tuple(), pd.DataFrame red_data=None, pd.DataFrame black_data=None, bool add_plot_column_titles=True)
list search_stats_time_cols
Contains classes that mirror the structure of some core C++ classes, primarily to facilitate easy IO ...