libs.commands.graph.personal
libs/commands/graph/personal.py
1""" 2libs/commands/graph/personal.py 3""" 4 5from typing import TYPE_CHECKING 6 7import matplotlib.pyplot as plt 8import pandas as pd 9import plotly.express as px # type: ignore 10import plotly.graph_objects as go # type: ignore 11from matplotlib import gridspec 12from plotly.subplots import make_subplots # type: ignore 13 14import libs.global_value as g 15from cls.timekit import ExtendedDatetime as ExtDt 16from libs.data import loader 17from libs.datamodels import GameInfo 18from libs.functions import compose, message 19from libs.types import StyleOptions 20from libs.utils import formatter, graphutil, textutil 21 22if TYPE_CHECKING: 23 from pathlib import Path 24 25 from integrations.protocols import MessageParserProtocol 26 27 28def plot(m: "MessageParserProtocol"): 29 """個人成績のグラフを生成する 30 31 Args: 32 m (MessageParserProtocol): メッセージデータ 33 """ 34 35 # データ収集 36 game_info = GameInfo() 37 g.params.update(guest_skip=g.params.get("guest_skip2")) 38 df = loader.read_data("SUMMARY_GAMEDATA") 39 40 if df.empty: 41 m.post.headline = {"0": message.random_reply(m, "no_hits")} 42 m.status.result = False 43 return 44 45 player = formatter.name_replace(g.params["player_name"], add_mark=True) 46 if g.params.get("anonymous"): 47 mapping_dict = formatter.anonymous_mapping([g.params["player_name"]]) 48 player = next(iter(mapping_dict.values())) 49 50 # 最終値(凡例/ラベル追加用) 51 point_sum = f"{float(df["point_sum"].iloc[-1]):+.1f}".replace("-", "▲") 52 point_avg = f"{float(df["point_avg"].iloc[-1]):+.1f}".replace("-", "▲") 53 rank_avg = f"{float(df["rank_avg"].iloc[-1]):.2f}" 54 total_game_count = int(df["count"].iloc[-1]) 55 56 title_text = f"『{player}』の成績" 57 if g.params.get("target_count", 0) == 0: 58 title_range = f"({ExtDt(g.params["starttime"]).format("ymdhm")} - {ExtDt(g.params["endtime"]).format("ymdhm")})" 59 else: 60 title_range = f"(直近 {len(df)} ゲーム)" 61 62 m.post.headline = {title_text: message.header(game_info, m)} 63 m.set_data( 64 "個人成績", formatter.df_rename(df.drop(columns=["count", "name"]), False), 65 StyleOptions(header_hidden=True, key_title=False) 66 ) 67 68 # --- グラフ生成 69 graphutil.setup() 70 match g.adapter.conf.plotting_backend: 71 case "plotly": 72 m.set_data("通算ポイント", plotly_point(df, title_range, total_game_count), StyleOptions()) 73 m.set_data("獲得順位", plotly_rank(df, title_range, total_game_count), StyleOptions()) 74 case "matplotlib": 75 save_file = textutil.save_file_path("graph.png") 76 fig = plt.figure(figsize=(12, 8)) 77 fig.suptitle(f"{title_text} {title_range}", fontsize=16) 78 fig.tight_layout() 79 80 grid = gridspec.GridSpec(nrows=2, ncols=1, height_ratios=[3, 1]) 81 point_ax = fig.add_subplot(grid[0]) 82 rank_ax = fig.add_subplot(grid[1], sharex=point_ax) 83 84 # ポイント 85 point_ax.plot(df["playtime"], df["point_sum"], marker="." if len(df) < 50 else None) 86 point_ax.plot(df["playtime"], df["point_avg"], marker="." if len(df) < 50 else None) 87 point_ax.bar(df["playtime"], df["point"], color="blue") 88 89 point_ax.tick_params(axis="x", which="both", labelbottom=False, bottom=False) 90 ylabs = point_ax.get_yticks()[1:-1] 91 point_ax.set_yticks(ylabs, [str(int(ylab)).replace("-", "▲") for ylab in ylabs]) 92 93 point_ax.legend( 94 [f"通算ポイント ({point_sum}pt)", f"平均ポイント ({point_avg}pt)", "獲得ポイント"], 95 bbox_to_anchor=(1, 1), 96 loc="upper left", 97 borderaxespad=0.5, 98 ) 99 100 point_ax.axhline(y=0, linewidth=0.5, ls="dashed", color="grey") 101 102 # 順位 103 rank_ax.plot(df["playtime"], df["rank"], marker="." if len(df) < 50 else None) 104 rank_ax.plot(df["playtime"], df["rank_avg"], marker="." if len(df) < 50 else None) 105 106 rank_ax.set_xlabel(graphutil.gen_xlabel(len(df))) 107 rank_ax.set_xticks(**graphutil.xticks_parameter(df["playtime"].to_list())) 108 rank_ax.set_ylim(ymin=0.85, ymax=4.15) 109 rank_ax.invert_yaxis() 110 111 rank_ax.legend( 112 ["獲得順位", f"平均順位 ({rank_avg})"], 113 bbox_to_anchor=(1, 1), 114 loc="upper left", 115 borderaxespad=0.5, 116 ) 117 118 rank_ax.axhline(y=2.5, linewidth=0.5, ls="dashed", color="grey") 119 120 plt.savefig(save_file, bbox_inches="tight") 121 m.set_data( 122 f"『{player}』の成績", save_file, 123 StyleOptions(use_comment=True, header_hidden=True, key_title=False), 124 ) 125 126 127def statistics_plot(m: "MessageParserProtocol"): 128 """個人成績の統計グラフを生成する 129 130 Args: 131 m (MessageParserProtocol): メッセージデータ 132 """ 133 134 # データ収集 135 game_info = GameInfo() 136 g.params.update(guest_skip=g.params.get("guest_skip2")) 137 df = loader.read_data("SUMMARY_DETAILS") 138 139 if df.empty: 140 m.post.headline = {"0": message.random_reply(m, "no_hits")} 141 m.status.result = False 142 return 143 144 if g.params.get("individual"): # 個人成績 145 player = formatter.name_replace(g.params["player_name"], add_mark=True) 146 else: # チーム成績 147 player = g.params["player_name"] 148 149 df = df.filter(items=["playtime", "name", "rpoint", "rank", "point"]) 150 df["rpoint"] = df["rpoint"] * 100 151 152 player_df = df.query("name == @player").reset_index(drop=True) 153 154 if player_df.empty: 155 m.post.headline = {"0": message.random_reply(m, "no_hits")} 156 m.status.result = False 157 return 158 159 player_df["sum_point"] = player_df["point"].cumsum() 160 161 if g.params.get("anonymous"): 162 mapping_dict = formatter.anonymous_mapping([g.params["player_name"]]) 163 player = next(iter(mapping_dict.values())) 164 165 title_text = f"『{player}』の成績 (検索範囲:{compose.text_item.date_range("ymd_o")})" 166 167 rpoint_df = get_data(player_df["rpoint"], g.params["interval"]) 168 point_sum_df = get_data(player_df["point"], g.params["interval"]) 169 point_df = get_data(player_df["sum_point"], g.params["interval"]).iloc[-1] 170 rank_df = get_data(player_df["rank"], g.params["interval"]) 171 total_index = "全区間" 172 173 rpoint_stats = { 174 "ゲーム数": rpoint_df.count().astype("int"), 175 "平均値(x)": rpoint_df.mean().round(1), 176 "最小値": rpoint_df.min().astype("int"), 177 "第一四分位数": rpoint_df.quantile(0.25).astype("int"), 178 "中央値(|)": rpoint_df.median().astype("int"), 179 "第三四分位数": rpoint_df.quantile(0.75).astype("int"), 180 "最大値": rpoint_df.max().astype("int"), 181 } 182 183 stats_df = pd.DataFrame(rpoint_stats) 184 stats_df.loc[total_index] = pd.Series( 185 { 186 "ゲーム数": int(player_df["rpoint"].count()), 187 "平均値(x)": float(round(player_df["rpoint"].mean(), 1)), 188 "最小値": int(player_df["rpoint"].min()), 189 "第一四分位数": int(player_df["rpoint"].quantile(0.25)), 190 "中央値(|)": int(player_df["rpoint"].median()), 191 "第三四分位数": int(player_df["rpoint"].quantile(0.75)), 192 "最大値": int(player_df["rpoint"].max()), 193 } 194 ) 195 stats_df = stats_df.apply(lambda col: col.map(lambda x: f"{int(x)}" if isinstance(x, int) else f"{x:.1f}")) 196 197 count_stats = { 198 "ゲーム数": rank_df.count().astype("int"), 199 "1位": rank_df[rank_df == 1].count().astype("int"), 200 "1位(%)": ((rank_df[rank_df == 1].count()) / rank_df.count()), 201 "2位": rank_df[rank_df == 2].count().astype("int"), 202 "2位(%)": ((rank_df[rank_df == 2].count()) / rank_df.count()), 203 "3位": rank_df[rank_df == 3].count().astype("int"), 204 "3位(%)": ((rank_df[rank_df == 3].count()) / rank_df.count()), 205 "4位": rank_df[rank_df == 4].count().astype("int"), 206 "4位(%)": ((rank_df[rank_df == 4].count()) / rank_df.count()), 207 "平均順位": rank_df.mean().round(2), 208 "区間ポイント": point_sum_df.sum().round(1), 209 "区間平均": point_sum_df.mean().round(1), 210 "通算ポイント": point_df.round(1), 211 } 212 count_df = pd.DataFrame(count_stats) 213 214 count_df.loc[total_index] = pd.Series( 215 { 216 "ゲーム数": int(count_df["ゲーム数"].sum()), 217 "1位": int(count_df["1位"].sum()), 218 "1位(%)": float(count_df["1位"].sum() / count_df["ゲーム数"].sum()), 219 "2位": int(count_df["2位"].sum()), 220 "2位(%)": float(count_df["2位"].sum() / count_df["ゲーム数"].sum()), 221 "3位": int(count_df["3位"].sum()), 222 "3位(%)": float(count_df["3位"].sum() / count_df["ゲーム数"].sum()), 223 "4位": int(count_df["4位"].sum()), 224 "4位(%)": float(count_df["4位"].sum() / count_df["ゲーム数"].sum()), 225 "平均順位": float(round(player_df["rank"].mean(), 2)), 226 "区間ポイント": float(round(player_df["point"].sum(), 1)), 227 "区間平均": float(round(player_df["point"].mean(), 1)), 228 } 229 ) 230 # テーブル用データ 231 rank_table = pd.DataFrame() 232 rank_table["ゲーム数"] = count_df["ゲーム数"].astype("int") 233 rank_table["1位"] = count_df.apply(lambda row: f"{row["1位(%)"]:.2%} ({row["1位"]:.0f})", axis=1) 234 rank_table["2位"] = count_df.apply(lambda row: f"{row["2位(%)"]:.2%} ({row["2位"]:.0f})", axis=1) 235 rank_table["3位"] = count_df.apply(lambda row: f"{row["3位(%)"]:.2%} ({row["3位"]:.0f})", axis=1) 236 rank_table["4位"] = count_df.apply(lambda row: f"{row["4位(%)"]:.2%} ({row["4位"]:.0f})", axis=1) 237 rank_table["平均順位"] = count_df.apply(lambda row: f"{row["平均順位"]:.2f}", axis=1) 238 239 m.post.headline = {f"『{player}』の成績": message.header(game_info, m)} 240 241 # --- グラフ生成 242 graphutil.setup() 243 match g.adapter.conf.plotting_backend: 244 case "plotly": 245 m.set_data("順位/ポイント情報", count_df, StyleOptions(show_index=True)) 246 m.set_data("通算ポイント", plotly_line("通算ポイント推移", point_df), StyleOptions()) 247 m.set_data("順位分布", plotly_bar("順位分布", count_df.drop(index=["全区間"])), StyleOptions()) 248 m.set_data("素点情報", stats_df, StyleOptions(show_index=True)) 249 m.set_data("素点分布", plotly_box("素点分布", rpoint_df), StyleOptions()) 250 case "matplotlib": 251 fig = plt.figure(figsize=(20, 10)) 252 fig.suptitle(title_text, size=20, weight="bold") 253 gs = gridspec.GridSpec(figure=fig, nrows=3, ncols=2) 254 255 ax_point1 = fig.add_subplot(gs[0, 0]) 256 ax_point2 = fig.add_subplot(gs[0, 1]) 257 ax_rank1 = fig.add_subplot(gs[1, 0]) 258 ax_rank2 = fig.add_subplot(gs[1, 1]) 259 ax_rpoint1 = fig.add_subplot(gs[2, 0]) 260 ax_rpoint2 = fig.add_subplot(gs[2, 1]) 261 262 plt.subplots_adjust(wspace=0.22, hspace=0.18) 263 264 # ポイントデータ 265 subplot_point(point_df, ax_point1) 266 subplot_table(count_df.filter(items=["ゲーム数", "区間ポイント", "区間平均", "通算ポイント"]), ax_point2) 267 268 # 順位データ 269 subplot_rank(count_df.copy(), ax_rank1, total_index) 270 subplot_table(rank_table, ax_rank2) 271 272 # 素点データ 273 subplot_box(rpoint_df, ax_rpoint1) 274 subplot_table(stats_df, ax_rpoint2) 275 276 save_file = textutil.save_file_path("graph.png") 277 plt.savefig(save_file, bbox_inches="tight") 278 279 m.set_data("個人成績", save_file, StyleOptions(use_comment=True, header_hidden=True)) 280 281 282def get_data(df: pd.Series, interval: int) -> pd.DataFrame: 283 """データフレームを指定範囲で分割する 284 285 Args: 286 df (pd.Series): 分割するデータ 287 interval (int): 1ブロックに収めるデータ数 288 289 Returns: 290 pd.DataFrame: 分割されたデータ 291 """ 292 293 # interval単位で分割 294 rpoint_data: dict = {} 295 296 fraction = 0 if not len(df) % interval else interval - len(df) % interval # 端数 297 if fraction: 298 df = pd.concat([pd.Series([None] * fraction), df], ignore_index=True) 299 300 for x in range(int(len(df) / interval)): 301 s = len(df) % interval + interval * x 302 e = s + interval 303 rpoint_data[f"{max(1, s + 1 - fraction):3d}G - {(e - fraction):3d}G"] = df.iloc[s:e].tolist() 304 305 return pd.DataFrame(rpoint_data) 306 307 308def subplot_box(df: pd.DataFrame, ax: plt.Axes) -> None: 309 """箱ひげ図を生成する 310 311 Args: 312 df (pd.DataFrame): プロットデータ 313 ax (plt.Axes): プロット先オブジェクト 314 """ 315 316 p = [x + 1 for x in range(len(df.columns))] 317 df.plot( 318 ax=ax, 319 kind="box", 320 title="素点分布", 321 showmeans=True, 322 meanprops={"marker": "x", "markeredgecolor": "b", "markerfacecolor": "b", "ms": 3}, 323 flierprops={"marker": ".", "markeredgecolor": "r"}, 324 ylabel="素点(点)", 325 sharex=True, 326 ) 327 ax.axhline(y=25000, linewidth=0.5, ls="dashed", color="grey") 328 ax.set_xticks(p) 329 ax.set_xticklabels(df.columns, rotation=45, ha="right") 330 331 # Y軸修正 332 ylabs = ax.get_yticks()[1:-1] 333 ax.set_yticks(ylabs) 334 ax.set_yticklabels( 335 [str(int(ylab)).replace("-", "▲") for ylab in ylabs] 336 ) 337 338 339def subplot_table(df: pd.DataFrame, ax: plt.Axes) -> None: 340 """テーブルを生成する 341 342 Args: 343 df (pd.DataFrame): プロットデータ 344 ax (plt.Axes): プロット先オブジェクト 345 """ 346 347 # 有効桁数の調整 348 for col in df.columns: 349 match col: 350 case "ゲーム数": 351 df[col] = df[col].apply(lambda x: int(float(x))) 352 case "区間ポイント" | "区間平均" | "通算ポイント": 353 df[col] = df[col].apply(lambda x: f"{float(x):+.1f}") 354 case "平均順位": 355 df[col] = df[col].apply(lambda x: f"{float(x):.2f}") 356 case "平均値(x)": 357 df[col] = df[col].apply(lambda x: f"{float(x):.1f}") 358 case "最小値" | "第一四分位数" | "中央値(|)" | "第三四分位数" | "最大値": 359 df[col] = df[col].apply(lambda x: int(float(x))) 360 361 df = df.apply(lambda col: col.map(lambda x: str(x).replace("-", "▲"))) 362 df.replace("+nan", "-----", inplace=True) 363 364 table = ax.table( 365 cellText=df.values.tolist(), 366 colLabels=df.columns.tolist(), 367 rowLabels=df.index.tolist(), 368 cellLoc="center", 369 loc="center", 370 ) 371 table.auto_set_font_size(False) 372 ax.axis("off") 373 374 375def subplot_point(df: pd.Series, ax: plt.Axes) -> None: 376 """ポイントデータ 377 378 Args: 379 df (pd.Series): プロットデータ 380 ax (plt.Axes): プロット先オブジェクト 381 """ 382 383 df.plot( # レイアウト調整用ダミー 384 ax=ax, 385 kind="bar", 386 alpha=0, 387 ) 388 df.plot( 389 ax=ax, 390 kind="line", 391 title="ポイント推移", 392 ylabel="通算ポイント(pt)", 393 marker="o", 394 color="b", 395 ) 396 # Y軸修正 397 ylabs = ax.get_yticks()[1:-1] 398 ax.set_yticks(ylabs) 399 ax.set_yticklabels( 400 [str(int(ylab)).replace("-", "▲") for ylab in ylabs] 401 ) 402 403 404def subplot_rank(df: pd.DataFrame, ax: plt.Axes, total_index: str) -> None: 405 """順位データ 406 407 Args: 408 df (pd.DataFrame): プロットデータ 409 ax (plt.Axes): プロット先オブジェクト 410 total_index (str): 合計値格納index 411 """ 412 413 df["1位(%)"] = df["1位(%)"] * 100 414 df["2位(%)"] = df["2位(%)"] * 100 415 df["3位(%)"] = df["3位(%)"] * 100 416 df["4位(%)"] = df["4位(%)"] * 100 417 418 ax_rank_avg = ax.twinx() 419 df.filter(items=["平均順位"]).drop(index=total_index).plot( 420 ax=ax_rank_avg, 421 kind="line", 422 ylabel="平均順位", 423 yticks=[1, 2, 3, 4], 424 ylim=[0.85, 4.15], 425 marker="o", 426 color="b", 427 legend=False, 428 grid=False, 429 ) 430 ax_rank_avg.invert_yaxis() 431 ax_rank_avg.axhline(y=2.5, linewidth=0.5, ls="dashed", color="grey") 432 433 filter_items = ["1位(%)", "2位(%)", "3位(%)", "4位(%)"] 434 df.filter(items=filter_items).drop(index=total_index).plot( 435 ax=ax, 436 kind="bar", 437 title="獲得順位", 438 ylabel="獲得順位(%)", 439 colormap="Set2", 440 stacked=True, 441 rot=90, 442 ylim=[-5, 105], 443 ) 444 h1, l1 = ax.get_legend_handles_labels() 445 h2, l2 = ax_rank_avg.get_legend_handles_labels() 446 ax.legend( 447 h1 + h2, 448 l1 + l2, 449 bbox_to_anchor=(0.5, 0), 450 loc="lower center", 451 ncol=5, 452 ) 453 454 455def plotly_point(df: pd.DataFrame, title_range: str, total_game_count: int) -> "Path": 456 """獲得ポイントグラフ(plotly用) 457 458 Args: 459 df (pd.DataFrame): プロットするデータ 460 title_range (str): 集計範囲(タイトル用) 461 total_game_count (int): ゲーム数 462 463 Returns: 464 Path: 保存先ファイルパス 465 """ 466 467 save_file = textutil.save_file_path("point.html") 468 469 fig = go.Figure() 470 fig.add_trace( 471 go.Scatter( 472 name="通算ポイント", 473 zorder=2, 474 mode="lines", 475 x=df["playtime"], 476 y=df["point_sum"], 477 ), 478 ) 479 fig.add_trace( 480 go.Bar( 481 name="獲得ポイント", 482 zorder=1, 483 x=df["playtime"], 484 y=df["point"], 485 marker_color=["darkgreen" if v >= 0 else "firebrick" for v in df["point"]], 486 ), 487 ) 488 489 fig.update_layout( 490 barmode="overlay", 491 title={ 492 "text": f"通算ポイント {title_range}", 493 "font": {"size": 30}, 494 "xref": "paper", 495 "xanchor": "center", 496 "x": 0.5, 497 }, 498 xaxis_title={ 499 "text": graphutil.gen_xlabel(total_game_count), 500 "font": {"size": 18}, 501 }, 502 legend_title=None, 503 ) 504 505 fig.update_yaxes( 506 title={ 507 "text": "ポイント(pt)", 508 "font": {"size": 18, "color": "white"}, 509 }, 510 ) 511 512 fig.write_html(save_file, full_html=False) 513 return save_file 514 515 516def plotly_rank(df: pd.DataFrame, title_range: str, total_game_count: int) -> "Path": 517 """獲得順位グラフ(plotly用) 518 519 Args: 520 df (pd.DataFrame): プロットするデータ 521 title_range (str): 集計範囲(タイトル用) 522 total_game_count (int): ゲーム数 523 524 Returns: 525 Path: 保存先ファイルパス 526 """ 527 528 save_file = textutil.save_file_path("rank.html") 529 530 fig = go.Figure() 531 fig.add_trace( 532 go.Scatter( 533 name="獲得順位", 534 zorder=1, 535 mode="lines", 536 x=df["playtime"], 537 y=df["rank"], 538 ), 539 ) 540 fig.add_trace( 541 go.Scatter( 542 name="平均順位", 543 zorder=2, 544 mode="lines", 545 x=df["playtime"], 546 y=df["rank_avg"], 547 line={"width": 5}, 548 ), 549 ) 550 551 fig.update_layout( 552 title={ 553 "text": f"獲得順位 {title_range}", 554 "font": {"size": 30}, 555 "xref": "paper", 556 "xanchor": "center", 557 "x": 0.5, 558 }, 559 xaxis_title={ 560 "text": graphutil.gen_xlabel(total_game_count), 561 "font": {"size": 18}, 562 }, 563 legend_title=None, 564 ) 565 566 fig.update_yaxes( 567 title={ 568 "text": "順位", 569 "font": {"size": 18, "color": "white"}, 570 }, 571 range=[4.2, 0.8], 572 tickvals=[4, 3, 2, 1], 573 zeroline=False, 574 ) 575 576 fig.add_hline( 577 y=2.5, 578 line_dash="dot", 579 line_color="white", 580 line_width=2, 581 layer="below", 582 ) 583 584 fig.write_html(save_file, full_html=False) 585 return save_file 586 587 588def plotly_line(title_text: str, df: pd.Series) -> "Path": 589 """通算ポイント推移グラフ生成(plotly用) 590 591 Args: 592 title_text (str): グラフタイトル 593 df (pd.DataFrame): プロットするデータ 594 595 Returns: 596 Path: 保存先ファイルパス 597 """ 598 599 save_file = textutil.save_file_path("point.html") 600 601 fig = go.Figure() 602 fig.add_traces( 603 go.Scatter( 604 mode="lines+markers", 605 x=df.index, 606 y=df.values, 607 ), 608 ) 609 610 fig.update_layout( 611 title={ 612 "text": title_text, 613 "font": {"size": 30}, 614 "xref": "paper", 615 "xanchor": "center", 616 "x": 0.5, 617 }, 618 xaxis_title={ 619 "text": "ゲーム区間", 620 "font": {"size": 18}, 621 }, 622 yaxis_title={ 623 "text": "ポイント(pt)", 624 "font": {"size": 18}, 625 }, 626 showlegend=False, 627 ) 628 fig.update_yaxes( 629 tickformat="d", 630 ) 631 632 fig.write_html(save_file, full_html=False) 633 return save_file 634 635 636def plotly_box(title_text: str, df: pd.DataFrame) -> "Path": 637 """素点分布グラフ生成(plotly用) 638 639 Args: 640 title_text (str): グラフタイトル 641 df (pd.DataFrame): プロットするデータ 642 643 Returns: 644 Path: 保存先ファイルパス 645 """ 646 647 save_file = textutil.save_file_path("rpoint.html") 648 fig = px.box(df) 649 fig.update_layout( 650 title={ 651 "text": title_text, 652 "font": {"size": 30}, 653 "xref": "paper", 654 "xanchor": "center", 655 "x": 0.5, 656 }, 657 xaxis_title={ 658 "text": "ゲーム区間", 659 "font": {"size": 18}, 660 }, 661 yaxis_title={ 662 "text": "素点(点)", 663 "font": {"size": 18}, 664 }, 665 ) 666 fig.update_yaxes( 667 zeroline=False, 668 tickformat="d", 669 ) 670 fig.add_hline( 671 y=25000, 672 line_dash="dot", 673 line_color="white", 674 line_width=1, 675 layer="below", 676 ) 677 678 fig.write_html(save_file, full_html=False) 679 return save_file 680 681 682def plotly_bar(title_text: str, df: pd.DataFrame) -> "Path": 683 """順位分布グラフ生成(plotly用) 684 685 Args: 686 title_text (str): グラフタイトル 687 df (pd.Series): プロットするデータ 688 689 Returns: 690 Path: 保存先ファイルパス 691 """ 692 693 save_file = textutil.save_file_path("rank.html") 694 695 fig = make_subplots(specs=[[{"secondary_y": True}]]) 696 # 獲得率 697 fig.add_trace(go.Bar(name="4位率", x=df.index, y=df["4位(%)"] * 100), secondary_y=False) 698 fig.add_trace(go.Bar(name="3位率", x=df.index, y=df["3位(%)"] * 100), secondary_y=False) 699 fig.add_trace(go.Bar(name="2位率", x=df.index, y=df["2位(%)"] * 100), secondary_y=False) 700 fig.add_trace(go.Bar(name="1位率", x=df.index, y=df["1位(%)"] * 100), secondary_y=False) 701 # 平均順位 702 fig.add_trace( 703 go.Scatter( 704 mode="lines+markers", 705 name="平均順位", 706 x=df.index, 707 y=df["平均順位"], 708 ), 709 secondary_y=True, 710 ) 711 712 fig.update_layout( 713 barmode="stack", 714 title={ 715 "text": title_text, 716 "font": {"size": 30}, 717 "xref": "paper", 718 "xanchor": "center", 719 "x": 0.5, 720 }, 721 xaxis_title={ 722 "text": "ゲーム区間", 723 "font": {"size": 18}, 724 }, 725 legend_traceorder="reversed", 726 legend_title=None, 727 legend={ 728 "xanchor": "center", 729 "yanchor": "bottom", 730 "orientation": "h", 731 "x": 0.5, 732 "y": 0.02, 733 }, 734 ) 735 fig.update_yaxes( # Y軸(左) 736 title={ 737 "text": "獲得順位(%)", 738 "font": {"size": 18, "color": "white"}, 739 }, 740 secondary_y=False, 741 zeroline=False, 742 ) 743 fig.update_yaxes( # Y軸(右) 744 secondary_y=True, 745 title={ 746 "text": "平均順位", 747 "font": {"size": 18, "color": "white"}, 748 }, 749 tickfont_color="white", 750 range=[4, 1], 751 showgrid=False, 752 zeroline=False, 753 ) 754 755 fig.write_html(save_file, full_html=False) 756 return save_file
29def plot(m: "MessageParserProtocol"): 30 """個人成績のグラフを生成する 31 32 Args: 33 m (MessageParserProtocol): メッセージデータ 34 """ 35 36 # データ収集 37 game_info = GameInfo() 38 g.params.update(guest_skip=g.params.get("guest_skip2")) 39 df = loader.read_data("SUMMARY_GAMEDATA") 40 41 if df.empty: 42 m.post.headline = {"0": message.random_reply(m, "no_hits")} 43 m.status.result = False 44 return 45 46 player = formatter.name_replace(g.params["player_name"], add_mark=True) 47 if g.params.get("anonymous"): 48 mapping_dict = formatter.anonymous_mapping([g.params["player_name"]]) 49 player = next(iter(mapping_dict.values())) 50 51 # 最終値(凡例/ラベル追加用) 52 point_sum = f"{float(df["point_sum"].iloc[-1]):+.1f}".replace("-", "▲") 53 point_avg = f"{float(df["point_avg"].iloc[-1]):+.1f}".replace("-", "▲") 54 rank_avg = f"{float(df["rank_avg"].iloc[-1]):.2f}" 55 total_game_count = int(df["count"].iloc[-1]) 56 57 title_text = f"『{player}』の成績" 58 if g.params.get("target_count", 0) == 0: 59 title_range = f"({ExtDt(g.params["starttime"]).format("ymdhm")} - {ExtDt(g.params["endtime"]).format("ymdhm")})" 60 else: 61 title_range = f"(直近 {len(df)} ゲーム)" 62 63 m.post.headline = {title_text: message.header(game_info, m)} 64 m.set_data( 65 "個人成績", formatter.df_rename(df.drop(columns=["count", "name"]), False), 66 StyleOptions(header_hidden=True, key_title=False) 67 ) 68 69 # --- グラフ生成 70 graphutil.setup() 71 match g.adapter.conf.plotting_backend: 72 case "plotly": 73 m.set_data("通算ポイント", plotly_point(df, title_range, total_game_count), StyleOptions()) 74 m.set_data("獲得順位", plotly_rank(df, title_range, total_game_count), StyleOptions()) 75 case "matplotlib": 76 save_file = textutil.save_file_path("graph.png") 77 fig = plt.figure(figsize=(12, 8)) 78 fig.suptitle(f"{title_text} {title_range}", fontsize=16) 79 fig.tight_layout() 80 81 grid = gridspec.GridSpec(nrows=2, ncols=1, height_ratios=[3, 1]) 82 point_ax = fig.add_subplot(grid[0]) 83 rank_ax = fig.add_subplot(grid[1], sharex=point_ax) 84 85 # ポイント 86 point_ax.plot(df["playtime"], df["point_sum"], marker="." if len(df) < 50 else None) 87 point_ax.plot(df["playtime"], df["point_avg"], marker="." if len(df) < 50 else None) 88 point_ax.bar(df["playtime"], df["point"], color="blue") 89 90 point_ax.tick_params(axis="x", which="both", labelbottom=False, bottom=False) 91 ylabs = point_ax.get_yticks()[1:-1] 92 point_ax.set_yticks(ylabs, [str(int(ylab)).replace("-", "▲") for ylab in ylabs]) 93 94 point_ax.legend( 95 [f"通算ポイント ({point_sum}pt)", f"平均ポイント ({point_avg}pt)", "獲得ポイント"], 96 bbox_to_anchor=(1, 1), 97 loc="upper left", 98 borderaxespad=0.5, 99 ) 100 101 point_ax.axhline(y=0, linewidth=0.5, ls="dashed", color="grey") 102 103 # 順位 104 rank_ax.plot(df["playtime"], df["rank"], marker="." if len(df) < 50 else None) 105 rank_ax.plot(df["playtime"], df["rank_avg"], marker="." if len(df) < 50 else None) 106 107 rank_ax.set_xlabel(graphutil.gen_xlabel(len(df))) 108 rank_ax.set_xticks(**graphutil.xticks_parameter(df["playtime"].to_list())) 109 rank_ax.set_ylim(ymin=0.85, ymax=4.15) 110 rank_ax.invert_yaxis() 111 112 rank_ax.legend( 113 ["獲得順位", f"平均順位 ({rank_avg})"], 114 bbox_to_anchor=(1, 1), 115 loc="upper left", 116 borderaxespad=0.5, 117 ) 118 119 rank_ax.axhline(y=2.5, linewidth=0.5, ls="dashed", color="grey") 120 121 plt.savefig(save_file, bbox_inches="tight") 122 m.set_data( 123 f"『{player}』の成績", save_file, 124 StyleOptions(use_comment=True, header_hidden=True, key_title=False), 125 )
個人成績のグラフを生成する
Arguments:
- m (MessageParserProtocol): メッセージデータ
128def statistics_plot(m: "MessageParserProtocol"): 129 """個人成績の統計グラフを生成する 130 131 Args: 132 m (MessageParserProtocol): メッセージデータ 133 """ 134 135 # データ収集 136 game_info = GameInfo() 137 g.params.update(guest_skip=g.params.get("guest_skip2")) 138 df = loader.read_data("SUMMARY_DETAILS") 139 140 if df.empty: 141 m.post.headline = {"0": message.random_reply(m, "no_hits")} 142 m.status.result = False 143 return 144 145 if g.params.get("individual"): # 個人成績 146 player = formatter.name_replace(g.params["player_name"], add_mark=True) 147 else: # チーム成績 148 player = g.params["player_name"] 149 150 df = df.filter(items=["playtime", "name", "rpoint", "rank", "point"]) 151 df["rpoint"] = df["rpoint"] * 100 152 153 player_df = df.query("name == @player").reset_index(drop=True) 154 155 if player_df.empty: 156 m.post.headline = {"0": message.random_reply(m, "no_hits")} 157 m.status.result = False 158 return 159 160 player_df["sum_point"] = player_df["point"].cumsum() 161 162 if g.params.get("anonymous"): 163 mapping_dict = formatter.anonymous_mapping([g.params["player_name"]]) 164 player = next(iter(mapping_dict.values())) 165 166 title_text = f"『{player}』の成績 (検索範囲:{compose.text_item.date_range("ymd_o")})" 167 168 rpoint_df = get_data(player_df["rpoint"], g.params["interval"]) 169 point_sum_df = get_data(player_df["point"], g.params["interval"]) 170 point_df = get_data(player_df["sum_point"], g.params["interval"]).iloc[-1] 171 rank_df = get_data(player_df["rank"], g.params["interval"]) 172 total_index = "全区間" 173 174 rpoint_stats = { 175 "ゲーム数": rpoint_df.count().astype("int"), 176 "平均値(x)": rpoint_df.mean().round(1), 177 "最小値": rpoint_df.min().astype("int"), 178 "第一四分位数": rpoint_df.quantile(0.25).astype("int"), 179 "中央値(|)": rpoint_df.median().astype("int"), 180 "第三四分位数": rpoint_df.quantile(0.75).astype("int"), 181 "最大値": rpoint_df.max().astype("int"), 182 } 183 184 stats_df = pd.DataFrame(rpoint_stats) 185 stats_df.loc[total_index] = pd.Series( 186 { 187 "ゲーム数": int(player_df["rpoint"].count()), 188 "平均値(x)": float(round(player_df["rpoint"].mean(), 1)), 189 "最小値": int(player_df["rpoint"].min()), 190 "第一四分位数": int(player_df["rpoint"].quantile(0.25)), 191 "中央値(|)": int(player_df["rpoint"].median()), 192 "第三四分位数": int(player_df["rpoint"].quantile(0.75)), 193 "最大値": int(player_df["rpoint"].max()), 194 } 195 ) 196 stats_df = stats_df.apply(lambda col: col.map(lambda x: f"{int(x)}" if isinstance(x, int) else f"{x:.1f}")) 197 198 count_stats = { 199 "ゲーム数": rank_df.count().astype("int"), 200 "1位": rank_df[rank_df == 1].count().astype("int"), 201 "1位(%)": ((rank_df[rank_df == 1].count()) / rank_df.count()), 202 "2位": rank_df[rank_df == 2].count().astype("int"), 203 "2位(%)": ((rank_df[rank_df == 2].count()) / rank_df.count()), 204 "3位": rank_df[rank_df == 3].count().astype("int"), 205 "3位(%)": ((rank_df[rank_df == 3].count()) / rank_df.count()), 206 "4位": rank_df[rank_df == 4].count().astype("int"), 207 "4位(%)": ((rank_df[rank_df == 4].count()) / rank_df.count()), 208 "平均順位": rank_df.mean().round(2), 209 "区間ポイント": point_sum_df.sum().round(1), 210 "区間平均": point_sum_df.mean().round(1), 211 "通算ポイント": point_df.round(1), 212 } 213 count_df = pd.DataFrame(count_stats) 214 215 count_df.loc[total_index] = pd.Series( 216 { 217 "ゲーム数": int(count_df["ゲーム数"].sum()), 218 "1位": int(count_df["1位"].sum()), 219 "1位(%)": float(count_df["1位"].sum() / count_df["ゲーム数"].sum()), 220 "2位": int(count_df["2位"].sum()), 221 "2位(%)": float(count_df["2位"].sum() / count_df["ゲーム数"].sum()), 222 "3位": int(count_df["3位"].sum()), 223 "3位(%)": float(count_df["3位"].sum() / count_df["ゲーム数"].sum()), 224 "4位": int(count_df["4位"].sum()), 225 "4位(%)": float(count_df["4位"].sum() / count_df["ゲーム数"].sum()), 226 "平均順位": float(round(player_df["rank"].mean(), 2)), 227 "区間ポイント": float(round(player_df["point"].sum(), 1)), 228 "区間平均": float(round(player_df["point"].mean(), 1)), 229 } 230 ) 231 # テーブル用データ 232 rank_table = pd.DataFrame() 233 rank_table["ゲーム数"] = count_df["ゲーム数"].astype("int") 234 rank_table["1位"] = count_df.apply(lambda row: f"{row["1位(%)"]:.2%} ({row["1位"]:.0f})", axis=1) 235 rank_table["2位"] = count_df.apply(lambda row: f"{row["2位(%)"]:.2%} ({row["2位"]:.0f})", axis=1) 236 rank_table["3位"] = count_df.apply(lambda row: f"{row["3位(%)"]:.2%} ({row["3位"]:.0f})", axis=1) 237 rank_table["4位"] = count_df.apply(lambda row: f"{row["4位(%)"]:.2%} ({row["4位"]:.0f})", axis=1) 238 rank_table["平均順位"] = count_df.apply(lambda row: f"{row["平均順位"]:.2f}", axis=1) 239 240 m.post.headline = {f"『{player}』の成績": message.header(game_info, m)} 241 242 # --- グラフ生成 243 graphutil.setup() 244 match g.adapter.conf.plotting_backend: 245 case "plotly": 246 m.set_data("順位/ポイント情報", count_df, StyleOptions(show_index=True)) 247 m.set_data("通算ポイント", plotly_line("通算ポイント推移", point_df), StyleOptions()) 248 m.set_data("順位分布", plotly_bar("順位分布", count_df.drop(index=["全区間"])), StyleOptions()) 249 m.set_data("素点情報", stats_df, StyleOptions(show_index=True)) 250 m.set_data("素点分布", plotly_box("素点分布", rpoint_df), StyleOptions()) 251 case "matplotlib": 252 fig = plt.figure(figsize=(20, 10)) 253 fig.suptitle(title_text, size=20, weight="bold") 254 gs = gridspec.GridSpec(figure=fig, nrows=3, ncols=2) 255 256 ax_point1 = fig.add_subplot(gs[0, 0]) 257 ax_point2 = fig.add_subplot(gs[0, 1]) 258 ax_rank1 = fig.add_subplot(gs[1, 0]) 259 ax_rank2 = fig.add_subplot(gs[1, 1]) 260 ax_rpoint1 = fig.add_subplot(gs[2, 0]) 261 ax_rpoint2 = fig.add_subplot(gs[2, 1]) 262 263 plt.subplots_adjust(wspace=0.22, hspace=0.18) 264 265 # ポイントデータ 266 subplot_point(point_df, ax_point1) 267 subplot_table(count_df.filter(items=["ゲーム数", "区間ポイント", "区間平均", "通算ポイント"]), ax_point2) 268 269 # 順位データ 270 subplot_rank(count_df.copy(), ax_rank1, total_index) 271 subplot_table(rank_table, ax_rank2) 272 273 # 素点データ 274 subplot_box(rpoint_df, ax_rpoint1) 275 subplot_table(stats_df, ax_rpoint2) 276 277 save_file = textutil.save_file_path("graph.png") 278 plt.savefig(save_file, bbox_inches="tight") 279 280 m.set_data("個人成績", save_file, StyleOptions(use_comment=True, header_hidden=True))
個人成績の統計グラフを生成する
Arguments:
- m (MessageParserProtocol): メッセージデータ
def
get_data( df: pandas.core.series.Series, interval: int) -> pandas.core.frame.DataFrame:
283def get_data(df: pd.Series, interval: int) -> pd.DataFrame: 284 """データフレームを指定範囲で分割する 285 286 Args: 287 df (pd.Series): 分割するデータ 288 interval (int): 1ブロックに収めるデータ数 289 290 Returns: 291 pd.DataFrame: 分割されたデータ 292 """ 293 294 # interval単位で分割 295 rpoint_data: dict = {} 296 297 fraction = 0 if not len(df) % interval else interval - len(df) % interval # 端数 298 if fraction: 299 df = pd.concat([pd.Series([None] * fraction), df], ignore_index=True) 300 301 for x in range(int(len(df) / interval)): 302 s = len(df) % interval + interval * x 303 e = s + interval 304 rpoint_data[f"{max(1, s + 1 - fraction):3d}G - {(e - fraction):3d}G"] = df.iloc[s:e].tolist() 305 306 return pd.DataFrame(rpoint_data)
データフレームを指定範囲で分割する
Arguments:
- df (pd.Series): 分割するデータ
- interval (int): 1ブロックに収めるデータ数
Returns:
pd.DataFrame: 分割されたデータ
def
subplot_box(df: pandas.core.frame.DataFrame, ax: matplotlib.axes._axes.Axes) -> None:
309def subplot_box(df: pd.DataFrame, ax: plt.Axes) -> None: 310 """箱ひげ図を生成する 311 312 Args: 313 df (pd.DataFrame): プロットデータ 314 ax (plt.Axes): プロット先オブジェクト 315 """ 316 317 p = [x + 1 for x in range(len(df.columns))] 318 df.plot( 319 ax=ax, 320 kind="box", 321 title="素点分布", 322 showmeans=True, 323 meanprops={"marker": "x", "markeredgecolor": "b", "markerfacecolor": "b", "ms": 3}, 324 flierprops={"marker": ".", "markeredgecolor": "r"}, 325 ylabel="素点(点)", 326 sharex=True, 327 ) 328 ax.axhline(y=25000, linewidth=0.5, ls="dashed", color="grey") 329 ax.set_xticks(p) 330 ax.set_xticklabels(df.columns, rotation=45, ha="right") 331 332 # Y軸修正 333 ylabs = ax.get_yticks()[1:-1] 334 ax.set_yticks(ylabs) 335 ax.set_yticklabels( 336 [str(int(ylab)).replace("-", "▲") for ylab in ylabs] 337 )
箱ひげ図を生成する
Arguments:
- df (pd.DataFrame): プロットデータ
- ax (plt.Axes): プロット先オブジェクト
def
subplot_table(df: pandas.core.frame.DataFrame, ax: matplotlib.axes._axes.Axes) -> None:
340def subplot_table(df: pd.DataFrame, ax: plt.Axes) -> None: 341 """テーブルを生成する 342 343 Args: 344 df (pd.DataFrame): プロットデータ 345 ax (plt.Axes): プロット先オブジェクト 346 """ 347 348 # 有効桁数の調整 349 for col in df.columns: 350 match col: 351 case "ゲーム数": 352 df[col] = df[col].apply(lambda x: int(float(x))) 353 case "区間ポイント" | "区間平均" | "通算ポイント": 354 df[col] = df[col].apply(lambda x: f"{float(x):+.1f}") 355 case "平均順位": 356 df[col] = df[col].apply(lambda x: f"{float(x):.2f}") 357 case "平均値(x)": 358 df[col] = df[col].apply(lambda x: f"{float(x):.1f}") 359 case "最小値" | "第一四分位数" | "中央値(|)" | "第三四分位数" | "最大値": 360 df[col] = df[col].apply(lambda x: int(float(x))) 361 362 df = df.apply(lambda col: col.map(lambda x: str(x).replace("-", "▲"))) 363 df.replace("+nan", "-----", inplace=True) 364 365 table = ax.table( 366 cellText=df.values.tolist(), 367 colLabels=df.columns.tolist(), 368 rowLabels=df.index.tolist(), 369 cellLoc="center", 370 loc="center", 371 ) 372 table.auto_set_font_size(False) 373 ax.axis("off")
テーブルを生成する
Arguments:
- df (pd.DataFrame): プロットデータ
- ax (plt.Axes): プロット先オブジェクト
def
subplot_point(df: pandas.core.series.Series, ax: matplotlib.axes._axes.Axes) -> None:
376def subplot_point(df: pd.Series, ax: plt.Axes) -> None: 377 """ポイントデータ 378 379 Args: 380 df (pd.Series): プロットデータ 381 ax (plt.Axes): プロット先オブジェクト 382 """ 383 384 df.plot( # レイアウト調整用ダミー 385 ax=ax, 386 kind="bar", 387 alpha=0, 388 ) 389 df.plot( 390 ax=ax, 391 kind="line", 392 title="ポイント推移", 393 ylabel="通算ポイント(pt)", 394 marker="o", 395 color="b", 396 ) 397 # Y軸修正 398 ylabs = ax.get_yticks()[1:-1] 399 ax.set_yticks(ylabs) 400 ax.set_yticklabels( 401 [str(int(ylab)).replace("-", "▲") for ylab in ylabs] 402 )
ポイントデータ
Arguments:
- df (pd.Series): プロットデータ
- ax (plt.Axes): プロット先オブジェクト
def
subplot_rank( df: pandas.core.frame.DataFrame, ax: matplotlib.axes._axes.Axes, total_index: str) -> None:
405def subplot_rank(df: pd.DataFrame, ax: plt.Axes, total_index: str) -> None: 406 """順位データ 407 408 Args: 409 df (pd.DataFrame): プロットデータ 410 ax (plt.Axes): プロット先オブジェクト 411 total_index (str): 合計値格納index 412 """ 413 414 df["1位(%)"] = df["1位(%)"] * 100 415 df["2位(%)"] = df["2位(%)"] * 100 416 df["3位(%)"] = df["3位(%)"] * 100 417 df["4位(%)"] = df["4位(%)"] * 100 418 419 ax_rank_avg = ax.twinx() 420 df.filter(items=["平均順位"]).drop(index=total_index).plot( 421 ax=ax_rank_avg, 422 kind="line", 423 ylabel="平均順位", 424 yticks=[1, 2, 3, 4], 425 ylim=[0.85, 4.15], 426 marker="o", 427 color="b", 428 legend=False, 429 grid=False, 430 ) 431 ax_rank_avg.invert_yaxis() 432 ax_rank_avg.axhline(y=2.5, linewidth=0.5, ls="dashed", color="grey") 433 434 filter_items = ["1位(%)", "2位(%)", "3位(%)", "4位(%)"] 435 df.filter(items=filter_items).drop(index=total_index).plot( 436 ax=ax, 437 kind="bar", 438 title="獲得順位", 439 ylabel="獲得順位(%)", 440 colormap="Set2", 441 stacked=True, 442 rot=90, 443 ylim=[-5, 105], 444 ) 445 h1, l1 = ax.get_legend_handles_labels() 446 h2, l2 = ax_rank_avg.get_legend_handles_labels() 447 ax.legend( 448 h1 + h2, 449 l1 + l2, 450 bbox_to_anchor=(0.5, 0), 451 loc="lower center", 452 ncol=5, 453 )
順位データ
Arguments:
- df (pd.DataFrame): プロットデータ
- ax (plt.Axes): プロット先オブジェクト
- total_index (str): 合計値格納index
def
plotly_point( df: pandas.core.frame.DataFrame, title_range: str, total_game_count: int) -> pathlib.Path:
456def plotly_point(df: pd.DataFrame, title_range: str, total_game_count: int) -> "Path": 457 """獲得ポイントグラフ(plotly用) 458 459 Args: 460 df (pd.DataFrame): プロットするデータ 461 title_range (str): 集計範囲(タイトル用) 462 total_game_count (int): ゲーム数 463 464 Returns: 465 Path: 保存先ファイルパス 466 """ 467 468 save_file = textutil.save_file_path("point.html") 469 470 fig = go.Figure() 471 fig.add_trace( 472 go.Scatter( 473 name="通算ポイント", 474 zorder=2, 475 mode="lines", 476 x=df["playtime"], 477 y=df["point_sum"], 478 ), 479 ) 480 fig.add_trace( 481 go.Bar( 482 name="獲得ポイント", 483 zorder=1, 484 x=df["playtime"], 485 y=df["point"], 486 marker_color=["darkgreen" if v >= 0 else "firebrick" for v in df["point"]], 487 ), 488 ) 489 490 fig.update_layout( 491 barmode="overlay", 492 title={ 493 "text": f"通算ポイント {title_range}", 494 "font": {"size": 30}, 495 "xref": "paper", 496 "xanchor": "center", 497 "x": 0.5, 498 }, 499 xaxis_title={ 500 "text": graphutil.gen_xlabel(total_game_count), 501 "font": {"size": 18}, 502 }, 503 legend_title=None, 504 ) 505 506 fig.update_yaxes( 507 title={ 508 "text": "ポイント(pt)", 509 "font": {"size": 18, "color": "white"}, 510 }, 511 ) 512 513 fig.write_html(save_file, full_html=False) 514 return save_file
獲得ポイントグラフ(plotly用)
Arguments:
- df (pd.DataFrame): プロットするデータ
- title_range (str): 集計範囲(タイトル用)
- total_game_count (int): ゲーム数
Returns:
Path: 保存先ファイルパス
def
plotly_rank( df: pandas.core.frame.DataFrame, title_range: str, total_game_count: int) -> pathlib.Path:
517def plotly_rank(df: pd.DataFrame, title_range: str, total_game_count: int) -> "Path": 518 """獲得順位グラフ(plotly用) 519 520 Args: 521 df (pd.DataFrame): プロットするデータ 522 title_range (str): 集計範囲(タイトル用) 523 total_game_count (int): ゲーム数 524 525 Returns: 526 Path: 保存先ファイルパス 527 """ 528 529 save_file = textutil.save_file_path("rank.html") 530 531 fig = go.Figure() 532 fig.add_trace( 533 go.Scatter( 534 name="獲得順位", 535 zorder=1, 536 mode="lines", 537 x=df["playtime"], 538 y=df["rank"], 539 ), 540 ) 541 fig.add_trace( 542 go.Scatter( 543 name="平均順位", 544 zorder=2, 545 mode="lines", 546 x=df["playtime"], 547 y=df["rank_avg"], 548 line={"width": 5}, 549 ), 550 ) 551 552 fig.update_layout( 553 title={ 554 "text": f"獲得順位 {title_range}", 555 "font": {"size": 30}, 556 "xref": "paper", 557 "xanchor": "center", 558 "x": 0.5, 559 }, 560 xaxis_title={ 561 "text": graphutil.gen_xlabel(total_game_count), 562 "font": {"size": 18}, 563 }, 564 legend_title=None, 565 ) 566 567 fig.update_yaxes( 568 title={ 569 "text": "順位", 570 "font": {"size": 18, "color": "white"}, 571 }, 572 range=[4.2, 0.8], 573 tickvals=[4, 3, 2, 1], 574 zeroline=False, 575 ) 576 577 fig.add_hline( 578 y=2.5, 579 line_dash="dot", 580 line_color="white", 581 line_width=2, 582 layer="below", 583 ) 584 585 fig.write_html(save_file, full_html=False) 586 return save_file
獲得順位グラフ(plotly用)
Arguments:
- df (pd.DataFrame): プロットするデータ
- title_range (str): 集計範囲(タイトル用)
- total_game_count (int): ゲーム数
Returns:
Path: 保存先ファイルパス
def
plotly_line(title_text: str, df: pandas.core.series.Series) -> pathlib.Path:
589def plotly_line(title_text: str, df: pd.Series) -> "Path": 590 """通算ポイント推移グラフ生成(plotly用) 591 592 Args: 593 title_text (str): グラフタイトル 594 df (pd.DataFrame): プロットするデータ 595 596 Returns: 597 Path: 保存先ファイルパス 598 """ 599 600 save_file = textutil.save_file_path("point.html") 601 602 fig = go.Figure() 603 fig.add_traces( 604 go.Scatter( 605 mode="lines+markers", 606 x=df.index, 607 y=df.values, 608 ), 609 ) 610 611 fig.update_layout( 612 title={ 613 "text": title_text, 614 "font": {"size": 30}, 615 "xref": "paper", 616 "xanchor": "center", 617 "x": 0.5, 618 }, 619 xaxis_title={ 620 "text": "ゲーム区間", 621 "font": {"size": 18}, 622 }, 623 yaxis_title={ 624 "text": "ポイント(pt)", 625 "font": {"size": 18}, 626 }, 627 showlegend=False, 628 ) 629 fig.update_yaxes( 630 tickformat="d", 631 ) 632 633 fig.write_html(save_file, full_html=False) 634 return save_file
通算ポイント推移グラフ生成(plotly用)
Arguments:
- title_text (str): グラフタイトル
- df (pd.DataFrame): プロットするデータ
Returns:
Path: 保存先ファイルパス
def
plotly_box(title_text: str, df: pandas.core.frame.DataFrame) -> pathlib.Path:
637def plotly_box(title_text: str, df: pd.DataFrame) -> "Path": 638 """素点分布グラフ生成(plotly用) 639 640 Args: 641 title_text (str): グラフタイトル 642 df (pd.DataFrame): プロットするデータ 643 644 Returns: 645 Path: 保存先ファイルパス 646 """ 647 648 save_file = textutil.save_file_path("rpoint.html") 649 fig = px.box(df) 650 fig.update_layout( 651 title={ 652 "text": title_text, 653 "font": {"size": 30}, 654 "xref": "paper", 655 "xanchor": "center", 656 "x": 0.5, 657 }, 658 xaxis_title={ 659 "text": "ゲーム区間", 660 "font": {"size": 18}, 661 }, 662 yaxis_title={ 663 "text": "素点(点)", 664 "font": {"size": 18}, 665 }, 666 ) 667 fig.update_yaxes( 668 zeroline=False, 669 tickformat="d", 670 ) 671 fig.add_hline( 672 y=25000, 673 line_dash="dot", 674 line_color="white", 675 line_width=1, 676 layer="below", 677 ) 678 679 fig.write_html(save_file, full_html=False) 680 return save_file
素点分布グラフ生成(plotly用)
Arguments:
- title_text (str): グラフタイトル
- df (pd.DataFrame): プロットするデータ
Returns:
Path: 保存先ファイルパス
def
plotly_bar(title_text: str, df: pandas.core.frame.DataFrame) -> pathlib.Path:
683def plotly_bar(title_text: str, df: pd.DataFrame) -> "Path": 684 """順位分布グラフ生成(plotly用) 685 686 Args: 687 title_text (str): グラフタイトル 688 df (pd.Series): プロットするデータ 689 690 Returns: 691 Path: 保存先ファイルパス 692 """ 693 694 save_file = textutil.save_file_path("rank.html") 695 696 fig = make_subplots(specs=[[{"secondary_y": True}]]) 697 # 獲得率 698 fig.add_trace(go.Bar(name="4位率", x=df.index, y=df["4位(%)"] * 100), secondary_y=False) 699 fig.add_trace(go.Bar(name="3位率", x=df.index, y=df["3位(%)"] * 100), secondary_y=False) 700 fig.add_trace(go.Bar(name="2位率", x=df.index, y=df["2位(%)"] * 100), secondary_y=False) 701 fig.add_trace(go.Bar(name="1位率", x=df.index, y=df["1位(%)"] * 100), secondary_y=False) 702 # 平均順位 703 fig.add_trace( 704 go.Scatter( 705 mode="lines+markers", 706 name="平均順位", 707 x=df.index, 708 y=df["平均順位"], 709 ), 710 secondary_y=True, 711 ) 712 713 fig.update_layout( 714 barmode="stack", 715 title={ 716 "text": title_text, 717 "font": {"size": 30}, 718 "xref": "paper", 719 "xanchor": "center", 720 "x": 0.5, 721 }, 722 xaxis_title={ 723 "text": "ゲーム区間", 724 "font": {"size": 18}, 725 }, 726 legend_traceorder="reversed", 727 legend_title=None, 728 legend={ 729 "xanchor": "center", 730 "yanchor": "bottom", 731 "orientation": "h", 732 "x": 0.5, 733 "y": 0.02, 734 }, 735 ) 736 fig.update_yaxes( # Y軸(左) 737 title={ 738 "text": "獲得順位(%)", 739 "font": {"size": 18, "color": "white"}, 740 }, 741 secondary_y=False, 742 zeroline=False, 743 ) 744 fig.update_yaxes( # Y軸(右) 745 secondary_y=True, 746 title={ 747 "text": "平均順位", 748 "font": {"size": 18, "color": "white"}, 749 }, 750 tickfont_color="white", 751 range=[4, 1], 752 showgrid=False, 753 zeroline=False, 754 ) 755 756 fig.write_html(save_file, full_html=False) 757 return save_file
順位分布グラフ生成(plotly用)
Arguments:
- title_text (str): グラフタイトル
- df (pd.Series): プロットするデータ
Returns:
Path: 保存先ファイルパス