libs.commands.graph.summary
libs/commands/graph/summary.py
1""" 2libs/commands/graph/summary.py 3""" 4 5import logging 6import os 7 8import matplotlib.font_manager as fm 9import matplotlib.pyplot as plt 10import pandas as pd 11 12import libs.global_value as g 13from cls.timekit import ExtendedDatetime as ExtDt 14from cls.types import GameInfoDict 15from libs.data import aggregate, loader 16from libs.functions import configuration, message 17from libs.utils import formatter 18 19 20def point_plot() -> tuple[int, str]: 21 """ポイント推移グラフを生成する 22 23 Returns: 24 tuple[int,str]: 25 - int: グラフにプロットしたゲーム数 26 - str: 検索結果が0件のときのメッセージ or グラフ画像保存パス 27 """ 28 29 plt.close() 30 # 初期化 31 title_text = None 32 xlabel_text = None 33 34 # データ収集 35 game_info: GameInfoDict = aggregate.game_info() 36 target_data, df = _data_collection() 37 38 if target_data.empty: # 描写対象が0人の場合は終了 39 return (len(target_data), message.reply(message="no_hits")) 40 41 # グラフタイトル/X軸ラベル 42 pivot_index = "playtime" 43 if g.params.get("target_count"): 44 title_text = f"ポイント推移 (直近 {g.params["target_count"]} ゲーム)" 45 xlabel_text = f"集計日(総ゲーム数:{game_info["game_count"]})" 46 else: 47 match g.params["collection"]: 48 case "daily": 49 xlabel_text = f"集計日(総ゲーム数:{game_info["game_count"]})" 50 title_text = message.item_date_range("ymd_o", "通算ポイント", "ポイント推移") 51 case "monthly": 52 xlabel_text = f"集計月(総ゲーム数:{game_info["game_count"]})" 53 title_text = message.item_date_range("jym_o", "通算ポイント", "ポイント推移") 54 case "yearly": 55 xlabel_text = f"集計年(総ゲーム数:{game_info["game_count"]})" 56 title_text = message.item_date_range("jy_o", "通算ポイント", "ポイント推移") 57 case "all": 58 xlabel_text = f"総ゲーム数:{game_info["game_count"]}" 59 title_text = message.item_date_range("ymdhm", "通算ポイント", "ポイント推移") 60 case _: 61 if g.params.get("search_word"): 62 pivot_index = "comment" 63 xlabel_text = f"(総ゲーム数:{game_info["game_count"]} )" 64 if game_info["first_comment"] == game_info["last_comment"]: 65 title_text = f"通算ポイント ({game_info["first_comment"]})" 66 else: 67 title_text = f"ポイント推移 ({game_info["first_comment"]} - {game_info["last_comment"]})" 68 else: 69 xlabel_text = f"ゲーム終了日時({game_info["game_count"]} ゲーム)" 70 title_text = message.item_date_range("ymdhm", "通算ポイント", "ポイント推移") 71 if ExtDt(g.params["starttime"]).format("ymd") == ExtDt(g.params["onday"]).format("ymd") and game_info["game_count"] == 1: 72 title_text = f"獲得ポイント ({ExtDt(g.params["starttime"]).format("ymd")})" 73 74 # 集計 75 if g.params.get("individual"): # 個人集計 76 legend = "name" 77 else: # チーム集計 78 legend = "team" 79 80 pivot = pd.pivot_table( 81 df, index=pivot_index, columns=legend, values="point_sum" 82 ).ffill() 83 pivot = pivot.reindex( # 並び替え 84 target_data[legend].to_list(), axis="columns" 85 ) 86 87 # グラフ生成 88 args = { 89 "kind": "point", 90 "title_text": title_text, 91 "total_game_count": game_info["game_count"], 92 "target_data": target_data, 93 "legend": legend, 94 "xlabel_text": xlabel_text, 95 "ylabel_text": "通算ポイント", 96 "horizontal": True, 97 } 98 save_file = _graph_generation(pivot, **args) 99 plt.savefig(save_file, bbox_inches="tight") 100 101 return (game_info["game_count"], save_file) 102 103 104def rank_plot() -> tuple[int, str]: 105 """順位変動グラフを生成する 106 107 Returns: 108 tuple[int,str]: 109 - int: グラフにプロットしたゲーム数 110 - str: 検索結果が0件のときのメッセージ or グラフ画像保存パス 111 """ 112 113 plt.close() 114 # 初期化 115 title_text = None 116 xlabel_text = None 117 118 # データ収集 119 game_info: GameInfoDict = aggregate.game_info() 120 target_data, df = _data_collection() 121 122 if target_data.empty: # 描写対象が0人の場合は終了 123 return (len(target_data), message.reply(message="no_hits")) 124 125 # グラフタイトル/X軸ラベル 126 pivot_index = "playtime" 127 if g.params.get("target_count"): 128 title_text = f"順位変動 (直近 {g.params["target_count"]} ゲーム)" 129 xlabel_text = f"集計日(総ゲーム数:{game_info["game_count"]})" 130 else: 131 match g.params["collection"]: 132 case "daily": 133 xlabel_text = f"集計日(総ゲーム数:{game_info["game_count"]})" 134 title_text = message.item_date_range("ymd_o", "順位", "順位変動") 135 case "monthly": 136 xlabel_text = f"集計月(総ゲーム数:{game_info["game_count"]})" 137 title_text = message.item_date_range("jym", "順位", "順位変動") 138 case "yearly": 139 xlabel_text = f"集計年(総ゲーム数:{game_info["game_count"]})" 140 title_text = message.item_date_range("jy", "順位", "順位変動") 141 case "all": 142 xlabel_text = f"総ゲーム数:{game_info["game_count"]}" 143 title_text = message.item_date_range("ymdhm", "順位", "順位変動") 144 case _: 145 if g.params.get("search_word"): 146 pivot_index = "comment" 147 xlabel_text = f"(総ゲーム数:{game_info["game_count"]} )" 148 if game_info["first_comment"] == game_info["last_comment"]: 149 title_text = f"順位 ({game_info["first_comment"]})" 150 else: 151 title_text = f"順位変動 ({game_info["first_comment"]} - {game_info["last_comment"]})" 152 else: 153 xlabel_text = f"ゲーム終了日時({game_info["game_count"]} ゲーム)" 154 title_text = message.item_date_range("ymdhm", "順位", "順位変動") 155 if ExtDt(g.params["starttime"]).format("ymd") == ExtDt(g.params["onday"]).format("ymd") and game_info["game_count"] == 1: 156 title_text = f"順位 ({ExtDt(g.params["starttime"]).format("ymd")})" 157 158 # 集計 159 if g.params.get("individual"): # 個人集計 160 legend = "name" 161 else: # チーム集計 162 legend = "team" 163 164 pivot = pd.pivot_table( 165 df, index=pivot_index, columns=legend, values="point_sum" 166 ).ffill() 167 pivot = pivot.reindex( # 並び替え 168 target_data[legend].to_list(), axis="columns" 169 ) 170 pivot = pivot.rank(method="dense", ascending=False, axis=1) 171 172 # グラフ生成 173 args = { 174 "kind": "rank", 175 "title_text": title_text, 176 "total_game_count": game_info["game_count"], 177 "target_data": target_data, 178 "legend": legend, 179 "xlabel_text": xlabel_text, 180 "ylabel_text": "順位 (通算ポイント順)", 181 "horizontal": False, 182 } 183 save_file = _graph_generation(pivot, **args) 184 plt.savefig(save_file, bbox_inches="tight") 185 186 return (game_info["game_count"], save_file) 187 188 189def _data_collection() -> tuple[pd.DataFrame, pd.DataFrame]: 190 """データ収集 191 192 Returns: 193 tuple[pd.DataFrame, pd.DataFrame]: 194 - pd.DataFrame: 収集したデータのサマリ 195 - pd.DataFrame: 集計範囲のデータ 196 """ 197 198 # データ収集 199 g.params.update(fourfold=True) # 直近Nは4倍する(縦持ちなので4人分) 200 201 target_data = pd.DataFrame() 202 if g.params.get("individual"): # 個人集計 203 df = loader.read_data("summary/gamedata.sql") 204 if df.empty: 205 return (target_data, df) 206 207 target_data["name"] = df.groupby("name", as_index=False).last()["name"] 208 target_data["last_point"] = df.groupby("name", as_index=False).last()["point_sum"] 209 target_data["game_count"] = df.groupby("name", as_index=False).max(numeric_only=True)["count"] 210 211 # 足切り 212 target_list = list( 213 target_data.query("game_count >= @g.params['stipulated']")["name"] 214 ) 215 _ = target_list # ignore PEP8 F841 216 target_data = target_data.query("name == @target_list").copy() 217 df = df.query("name == @target_list").copy() 218 else: # チーム集計 219 df = loader.read_data("summary/gamedata.sql") 220 if df.empty: 221 return (target_data, df) 222 223 target_data["last_point"] = df.groupby("team").last()["point_sum"] 224 target_data["game_count"] = ( 225 df.groupby("team").max(numeric_only=True)["count"] 226 ) 227 target_data["team"] = target_data.index 228 target_data = target_data.sort_values("last_point", ascending=False) 229 230 # 順位付け 231 target_data["position"] = ( 232 target_data["last_point"].rank(ascending=False).astype(int) 233 ) 234 235 if g.params.get("anonymous"): 236 col = "team" 237 if g.params.get("individual"): 238 col = "name" 239 mapping_dict = formatter.anonymous_mapping(df[col].unique().tolist()) 240 df[col] = df[col].replace(mapping_dict) 241 target_data[col] = target_data[col].replace(mapping_dict) 242 243 return (target_data.sort_values("position"), df) 244 245 246def _graph_generation(df: pd.DataFrame, **kwargs) -> str: 247 """グラフ生成共通処理 248 249 Args: 250 df (pd.DataFrame): _description_ 251 kwargs (dict): グラフ生成パラメータ 252 253 Returns: 254 str: 生成したグラフの保存パス 255 """ 256 257 save_file = os.path.join( 258 g.cfg.setting.work_dir, 259 f"{g.params["filename"]}.png" if g.params.get("filename") else "graph.png", 260 ) 261 262 configuration.graph_setup(plt, fm) 263 264 if (all(df.count() == 1) or g.params["collection"] == "all") and kwargs["horizontal"]: 265 kwargs["kind"] = "barh" 266 lab: list = [] 267 color: list = [] 268 for _, v in kwargs["target_data"].iterrows(): 269 lab.append("{:2d}位:{} ({}pt / {}G)".format( # pylint: disable=consider-using-f-string 270 v["position"], 271 v[kwargs["legend"]], 272 "{:+.1f}".format(v["last_point"]).replace("-", "▲"), # pylint: disable=consider-using-f-string 273 v["game_count"], 274 )) 275 if v["last_point"] > 0: 276 color.append("deepskyblue") 277 else: 278 color.append("orangered") 279 280 tmpdf = pd.DataFrame( 281 {"point": kwargs["target_data"]["last_point"].to_list()[::-1]}, 282 index=lab[::-1], 283 ) 284 285 tmpdf.plot.barh( 286 figsize=(8, 2 + tmpdf.count().iloc[0] / 5), 287 y="point", 288 xlabel=f"総ゲーム数:{kwargs["total_game_count"]}", 289 color=color[::-1], 290 ) 291 292 plt.legend().remove() 293 plt.gca().yaxis.tick_right() 294 295 # X軸修正 296 xlocs, xlabs = plt.xticks() 297 new_xlabs = [xlab.get_text().replace("−", "▲") for xlab in xlabs] 298 plt.xticks(list(xlocs[1:-1]), new_xlabs[1:-1]) 299 300 logging.info("plot data:\n%s", tmpdf) 301 else: 302 df.plot( 303 figsize=(8, 6), 304 xlabel=kwargs["xlabel_text"], 305 ylabel=kwargs["ylabel_text"], 306 marker="." if len(df) < 50 else None, 307 ) 308 309 # 凡例 310 legend_text = [] 311 for _, v in kwargs["target_data"].iterrows(): 312 legend_text.append("{:2d}位:{} ({}pt / {}G)".format( # pylint: disable=consider-using-f-string 313 v["position"], v[kwargs["legend"]], 314 "{:+.1f}".format(v["last_point"]).replace("-", "▲"), # pylint: disable=consider-using-f-string 315 v["game_count"], 316 )) 317 318 plt.legend( 319 legend_text, 320 bbox_to_anchor=(1, 1), 321 loc="upper left", 322 borderaxespad=0.5, 323 ncol=int(len(kwargs["target_data"]) / 25 + 1), 324 ) 325 326 # X軸修正 327 plt.xticks( 328 list(range(len(df)))[::int(len(df) / 25) + 1], 329 list(df.index)[::int(len(df) / 25) + 1], 330 rotation=45, 331 ha="right", 332 ) 333 334 # Y軸修正 335 ylocs, ylabs = plt.yticks() 336 new_ylabs = [ylab.get_text().replace("−", "▲") for ylab in ylabs] 337 plt.yticks(list(ylocs[1:-1]), new_ylabs[1:-1]) 338 339 logging.info("plot data:\n%s", df) 340 341 # 342 match kwargs["kind"]: 343 case "barh": 344 plt.axvline(x=0, linewidth=0.5, ls="dashed", color="grey") 345 case "point": 346 plt.axhline(y=0, linewidth=0.5, ls="dashed", color="grey") 347 case "rank": 348 lab = list(range(len(kwargs["target_data"]) + 1)) 349 if len(lab) > 10: 350 plt.yticks(lab[1::2], lab[1::2]) 351 else: 352 plt.yticks(lab[1:], lab[1:]) 353 plt.gca().invert_yaxis() 354 355 plt.title( 356 kwargs["title_text"], 357 fontsize=16, 358 ) 359 360 return save_file
def
point_plot() -> tuple[int, str]:
21def point_plot() -> tuple[int, str]: 22 """ポイント推移グラフを生成する 23 24 Returns: 25 tuple[int,str]: 26 - int: グラフにプロットしたゲーム数 27 - str: 検索結果が0件のときのメッセージ or グラフ画像保存パス 28 """ 29 30 plt.close() 31 # 初期化 32 title_text = None 33 xlabel_text = None 34 35 # データ収集 36 game_info: GameInfoDict = aggregate.game_info() 37 target_data, df = _data_collection() 38 39 if target_data.empty: # 描写対象が0人の場合は終了 40 return (len(target_data), message.reply(message="no_hits")) 41 42 # グラフタイトル/X軸ラベル 43 pivot_index = "playtime" 44 if g.params.get("target_count"): 45 title_text = f"ポイント推移 (直近 {g.params["target_count"]} ゲーム)" 46 xlabel_text = f"集計日(総ゲーム数:{game_info["game_count"]})" 47 else: 48 match g.params["collection"]: 49 case "daily": 50 xlabel_text = f"集計日(総ゲーム数:{game_info["game_count"]})" 51 title_text = message.item_date_range("ymd_o", "通算ポイント", "ポイント推移") 52 case "monthly": 53 xlabel_text = f"集計月(総ゲーム数:{game_info["game_count"]})" 54 title_text = message.item_date_range("jym_o", "通算ポイント", "ポイント推移") 55 case "yearly": 56 xlabel_text = f"集計年(総ゲーム数:{game_info["game_count"]})" 57 title_text = message.item_date_range("jy_o", "通算ポイント", "ポイント推移") 58 case "all": 59 xlabel_text = f"総ゲーム数:{game_info["game_count"]}" 60 title_text = message.item_date_range("ymdhm", "通算ポイント", "ポイント推移") 61 case _: 62 if g.params.get("search_word"): 63 pivot_index = "comment" 64 xlabel_text = f"(総ゲーム数:{game_info["game_count"]} )" 65 if game_info["first_comment"] == game_info["last_comment"]: 66 title_text = f"通算ポイント ({game_info["first_comment"]})" 67 else: 68 title_text = f"ポイント推移 ({game_info["first_comment"]} - {game_info["last_comment"]})" 69 else: 70 xlabel_text = f"ゲーム終了日時({game_info["game_count"]} ゲーム)" 71 title_text = message.item_date_range("ymdhm", "通算ポイント", "ポイント推移") 72 if ExtDt(g.params["starttime"]).format("ymd") == ExtDt(g.params["onday"]).format("ymd") and game_info["game_count"] == 1: 73 title_text = f"獲得ポイント ({ExtDt(g.params["starttime"]).format("ymd")})" 74 75 # 集計 76 if g.params.get("individual"): # 個人集計 77 legend = "name" 78 else: # チーム集計 79 legend = "team" 80 81 pivot = pd.pivot_table( 82 df, index=pivot_index, columns=legend, values="point_sum" 83 ).ffill() 84 pivot = pivot.reindex( # 並び替え 85 target_data[legend].to_list(), axis="columns" 86 ) 87 88 # グラフ生成 89 args = { 90 "kind": "point", 91 "title_text": title_text, 92 "total_game_count": game_info["game_count"], 93 "target_data": target_data, 94 "legend": legend, 95 "xlabel_text": xlabel_text, 96 "ylabel_text": "通算ポイント", 97 "horizontal": True, 98 } 99 save_file = _graph_generation(pivot, **args) 100 plt.savefig(save_file, bbox_inches="tight") 101 102 return (game_info["game_count"], save_file)
ポイント推移グラフを生成する
Returns:
tuple[int,str]:
- int: グラフにプロットしたゲーム数
- str: 検索結果が0件のときのメッセージ or グラフ画像保存パス
def
rank_plot() -> tuple[int, str]:
105def rank_plot() -> tuple[int, str]: 106 """順位変動グラフを生成する 107 108 Returns: 109 tuple[int,str]: 110 - int: グラフにプロットしたゲーム数 111 - str: 検索結果が0件のときのメッセージ or グラフ画像保存パス 112 """ 113 114 plt.close() 115 # 初期化 116 title_text = None 117 xlabel_text = None 118 119 # データ収集 120 game_info: GameInfoDict = aggregate.game_info() 121 target_data, df = _data_collection() 122 123 if target_data.empty: # 描写対象が0人の場合は終了 124 return (len(target_data), message.reply(message="no_hits")) 125 126 # グラフタイトル/X軸ラベル 127 pivot_index = "playtime" 128 if g.params.get("target_count"): 129 title_text = f"順位変動 (直近 {g.params["target_count"]} ゲーム)" 130 xlabel_text = f"集計日(総ゲーム数:{game_info["game_count"]})" 131 else: 132 match g.params["collection"]: 133 case "daily": 134 xlabel_text = f"集計日(総ゲーム数:{game_info["game_count"]})" 135 title_text = message.item_date_range("ymd_o", "順位", "順位変動") 136 case "monthly": 137 xlabel_text = f"集計月(総ゲーム数:{game_info["game_count"]})" 138 title_text = message.item_date_range("jym", "順位", "順位変動") 139 case "yearly": 140 xlabel_text = f"集計年(総ゲーム数:{game_info["game_count"]})" 141 title_text = message.item_date_range("jy", "順位", "順位変動") 142 case "all": 143 xlabel_text = f"総ゲーム数:{game_info["game_count"]}" 144 title_text = message.item_date_range("ymdhm", "順位", "順位変動") 145 case _: 146 if g.params.get("search_word"): 147 pivot_index = "comment" 148 xlabel_text = f"(総ゲーム数:{game_info["game_count"]} )" 149 if game_info["first_comment"] == game_info["last_comment"]: 150 title_text = f"順位 ({game_info["first_comment"]})" 151 else: 152 title_text = f"順位変動 ({game_info["first_comment"]} - {game_info["last_comment"]})" 153 else: 154 xlabel_text = f"ゲーム終了日時({game_info["game_count"]} ゲーム)" 155 title_text = message.item_date_range("ymdhm", "順位", "順位変動") 156 if ExtDt(g.params["starttime"]).format("ymd") == ExtDt(g.params["onday"]).format("ymd") and game_info["game_count"] == 1: 157 title_text = f"順位 ({ExtDt(g.params["starttime"]).format("ymd")})" 158 159 # 集計 160 if g.params.get("individual"): # 個人集計 161 legend = "name" 162 else: # チーム集計 163 legend = "team" 164 165 pivot = pd.pivot_table( 166 df, index=pivot_index, columns=legend, values="point_sum" 167 ).ffill() 168 pivot = pivot.reindex( # 並び替え 169 target_data[legend].to_list(), axis="columns" 170 ) 171 pivot = pivot.rank(method="dense", ascending=False, axis=1) 172 173 # グラフ生成 174 args = { 175 "kind": "rank", 176 "title_text": title_text, 177 "total_game_count": game_info["game_count"], 178 "target_data": target_data, 179 "legend": legend, 180 "xlabel_text": xlabel_text, 181 "ylabel_text": "順位 (通算ポイント順)", 182 "horizontal": False, 183 } 184 save_file = _graph_generation(pivot, **args) 185 plt.savefig(save_file, bbox_inches="tight") 186 187 return (game_info["game_count"], save_file)
順位変動グラフを生成する
Returns:
tuple[int,str]:
- int: グラフにプロットしたゲーム数
- str: 検索結果が0件のときのメッセージ or グラフ画像保存パス