libs.domain.aggregate
libs/domain/aggregate.py
1""" 2libs/domain/aggregate.py 3""" 4 5from typing import Any, Optional 6 7import numpy as np 8import pandas as pd 9 10import libs.global_value as g 11from libs.utils import formatter 12 13 14def game_summary( 15 filter_items: Optional[list[str]] = None, 16 drop_items: Optional[list[str]] = None, 17) -> pd.DataFrame: 18 """ 19 ゲーム結果をサマライズする 20 21 Args: 22 filter_items (Optional[list[str]]): 抽出するカラム. Defaults to None. 23 drop_items (Optional[list[str]]): 除外するカラム. Defaults to None. 24 25 Returns: 26 pd.DataFrame: 集計結果 27 28 """ 29 # データ収集 30 df = g.params.read_data("SUMMARY_TOTAL") 31 32 # 順位分布選択 33 match g.params.mode: 34 case 3: 35 df = df.drop(columns=["rank_distr4"]) 36 case 4: 37 df = df.drop(columns=["rank_distr3"]) 38 39 if isinstance(filter_items, list): 40 df = df.filter(items=filter_items) 41 42 if isinstance(drop_items, list): 43 df = df.drop(columns=drop_items) 44 45 return df 46 47 48def calculation_rating() -> pd.DataFrame: 49 """ 50 レーティング集計 51 52 Returns: 53 pd.DataFrame: 集計結果 54 55 """ 56 # データ収集 57 df_results = g.params.read_data("RANKING_RATINGS").set_index("playtime") 58 df_ratings = pd.DataFrame(index=["initial_rating"] + df_results.index.to_list()) # 記録用 59 last_ratings: dict[str, float] = {} # 最終値格納用 60 61 # 獲得スコア 62 score_mapping = {"1": 30.0, "2": 10.0, "3": -10.0, "4": -30.0} 63 64 for x in df_results.itertuples(): 65 player_list = (str(x.p1_name), str(x.p2_name), str(x.p3_name), str(x.p4_name)) 66 for player in player_list: 67 if player not in df_ratings.columns: 68 last_ratings[player] = 1500.0 69 df_ratings[player] = np.nan 70 df_ratings.loc["initial_rating", player] = 1500.0 71 df_ratings = df_ratings 72 73 # 天鳳計算式 (https://tenhou.net/man/#RATING) 74 rank_list = (x.p1_rank, x.p2_rank, x.p3_rank, x.p4_rank) 75 rating_list = [last_ratings[player] for player in player_list] 76 rating_avg = float(1500.0 if np.mean(rating_list) < 1500.0 else np.mean(rating_list)) 77 78 for i, player in enumerate(player_list): 79 rating = float(rating_list[i]) 80 rank = str(rank_list[i]) 81 82 correction_value: float = (rating_avg - rating) / 40 83 if df_ratings[player].count() >= 400: 84 match_correction = 0.2 85 else: 86 match_correction = 1 - df_ratings[player].count() * 0.002 87 88 new_rating = rating + match_correction * (score_mapping[rank] + correction_value) 89 90 last_ratings[player] = new_rating 91 df_ratings.loc[x.Index, player] = new_rating 92 93 # 間引き(集約オプション) 94 if collection := g.params.collection: 95 ratings = df_ratings[1:] 96 ratings.index = pd.to_datetime(ratings.index) # DatetimeIndexに変換 97 98 match collection: 99 case "daily": 100 ratings = ratings.resample("D").last().ffill() 101 case "monthly": 102 ratings = ratings.resample("ME").last().ffill() 103 case "yearly": 104 ratings = ratings.resample("YE").last().ffill() 105 case "all": 106 ratings = df_ratings.ffill().tail(1) 107 case _: 108 return df_ratings 109 110 ratings.index = ratings.index.astype(str) 111 df_ratings = pd.concat([df_ratings.head(1), ratings]) 112 113 return df_ratings 114 115 116def grade_promotion_check(grade_level: int, point: int, rank: int) -> tuple[int, int]: 117 """ 118 昇段チェック 119 120 Args: 121 grade_level (int): 現在のレベル(段位) 122 point (int): 現在の昇段ポイント 123 rank (int): 獲得順位 124 125 Returns: 126 tuple[int, int]: チェック後の昇段ポイント, チェック後のレベル(段位) 127 128 """ 129 tbl_data = g.cfg.badge.grade.table["table"] 130 new_point = point + int(tbl_data[grade_level]["acquisition"][rank - 1]) 131 132 if new_point >= int(tbl_data[grade_level]["point"][1]): # level up 133 grade_level = min(grade_level + 1, len(tbl_data) - 1) 134 new_point = int(tbl_data[grade_level]["point"][0]) # 初期値 135 elif new_point < 0: # level down 136 new_point = int(0) 137 if tbl_data[grade_level]["demote"]: 138 grade_level = max(grade_level - 1, 0) 139 new_point = int(tbl_data[grade_level]["point"][0]) # 初期値 140 141 return (new_point, grade_level) 142 143 144# レポート 145def matrix_table() -> pd.DataFrame: 146 """ 147 対局対戦マトリックス表の作成 148 149 Returns: 150 pd.DataFrame: 集計結果 151 152 """ 153 # データ収集 154 df = g.params.read_data("REPORT_MATRIX_TABLE").set_index("playtime") 155 156 # 結果に含まれるプレイヤーのリスト 157 plist = sorted(list(set(df["p1_name"].tolist() + df["p2_name"].tolist() + df["p3_name"].tolist() + df["p4_name"].tolist()))) 158 159 # 順位テーブルの作成 160 l_data: dict[str, Any] = {} 161 for pname in plist: 162 if g.params.individual: # 個人集計 163 l_name = formatter.name_replace(pname) 164 # プレイヤー指定があるなら対象以外をスキップ 165 if g.params.player_list: 166 if l_name not in g.params.player_list: 167 continue 168 # ゲスト置換 169 if g.params.guest_skip: # ゲストあり 170 l_name = formatter.name_replace(pname, add_mark=True) 171 else: # ゲストなし 172 if pname == g.cfg.member.guest_name: 173 continue 174 else: # チーム集計 175 l_name = pname 176 177 l_data[l_name] = [] 178 for x in df.itertuples(): 179 match pname: 180 case x.p1_name: 181 l_data[l_name] += [x.p1_rank] 182 case x.p2_name: 183 l_data[l_name] += [x.p2_rank] 184 case x.p3_name: 185 l_data[l_name] += [x.p3_rank] 186 case x.p4_name: 187 l_data[l_name] += [x.p4_rank] 188 case _: 189 l_data[l_name] += [None] 190 191 # 規定打数以下を足切り 192 if g.params.stipulated: 193 for pname in list(l_data.keys()): 194 if sum(x is not None for x in l_data[pname]) < g.params.stipulated: 195 l_data.pop(pname) 196 197 rank_df = pd.DataFrame(l_data.values(), columns=list(df.index), index=list(l_data.keys())) 198 199 # 対象リストが0件になった場合は空のデータフレームを返す 200 if rank_df.empty: 201 return rank_df 202 203 # 対局対戦マトリックス表の作成 204 mtx_df = pd.DataFrame(index=list(l_data.keys()), columns=list(l_data.keys()) + ["total"]) 205 sorting_df = pd.DataFrame(index=list(l_data.keys()), columns=["win_per", "count"]) 206 207 for idx1 in range(len(rank_df)): 208 p1 = rank_df.iloc[idx1] 209 t_game_count = 0 210 t_win = 0 211 for idx2 in range(len(rank_df)): 212 p2 = rank_df.iloc[idx2] 213 if p1.name == p2.name: 214 mtx_df.loc[f"{p1.name}", f"{p2.name}"] = "---" 215 else: 216 game_count = len(pd.concat([p1, p2], axis=1).dropna()) 217 win = (p1 < p2).sum() 218 t_game_count += game_count 219 t_win += win 220 221 if game_count: 222 winning_per = str(round(float(win / game_count * 100), 1)) 223 else: 224 winning_per = "--.-" 225 mtx_df.loc[f"{p1.name}", f"{p2.name}"] = f"{win}-{game_count - win} ({winning_per}%)" 226 227 if t_game_count: 228 t_winning_per = str(round(float(t_win / t_game_count * 100), 1)) 229 else: 230 t_winning_per = "--.-" 231 mtx_df.loc[f"{p1.name}", "total"] = f"{t_win}-{t_game_count - t_win} ({t_winning_per}%)" 232 sorting_df.loc[f"{p1.name}", "win_per"] = t_winning_per 233 sorting_df.loc[f"{p1.name}", "count"] = t_game_count 234 235 # 勝率で並び替え 236 sorting_df["win_per"] = pd.to_numeric(sorting_df["win_per"], errors="coerce") 237 sorting_df["count"] = pd.to_numeric(sorting_df["count"], errors="coerce") 238 sorting_df = sorting_df.sort_values(by=["win_per", "count"], ascending=[False, False]) 239 mtx_df = mtx_df.reindex(index=list(sorting_df.index), columns=list(sorting_df.index) + ["total"]) 240 241 return mtx_df
def
game_summary( filter_items: list[str] | None = None, drop_items: list[str] | None = None) -> pandas.DataFrame:
15def game_summary( 16 filter_items: Optional[list[str]] = None, 17 drop_items: Optional[list[str]] = None, 18) -> pd.DataFrame: 19 """ 20 ゲーム結果をサマライズする 21 22 Args: 23 filter_items (Optional[list[str]]): 抽出するカラム. Defaults to None. 24 drop_items (Optional[list[str]]): 除外するカラム. Defaults to None. 25 26 Returns: 27 pd.DataFrame: 集計結果 28 29 """ 30 # データ収集 31 df = g.params.read_data("SUMMARY_TOTAL") 32 33 # 順位分布選択 34 match g.params.mode: 35 case 3: 36 df = df.drop(columns=["rank_distr4"]) 37 case 4: 38 df = df.drop(columns=["rank_distr3"]) 39 40 if isinstance(filter_items, list): 41 df = df.filter(items=filter_items) 42 43 if isinstance(drop_items, list): 44 df = df.drop(columns=drop_items) 45 46 return df
ゲーム結果をサマライズする
Arguments:
- filter_items (Optional[list[str]]): 抽出するカラム. Defaults to None.
- drop_items (Optional[list[str]]): 除外するカラム. Defaults to None.
Returns:
pd.DataFrame: 集計結果
def
calculation_rating() -> pandas.DataFrame:
49def calculation_rating() -> pd.DataFrame: 50 """ 51 レーティング集計 52 53 Returns: 54 pd.DataFrame: 集計結果 55 56 """ 57 # データ収集 58 df_results = g.params.read_data("RANKING_RATINGS").set_index("playtime") 59 df_ratings = pd.DataFrame(index=["initial_rating"] + df_results.index.to_list()) # 記録用 60 last_ratings: dict[str, float] = {} # 最終値格納用 61 62 # 獲得スコア 63 score_mapping = {"1": 30.0, "2": 10.0, "3": -10.0, "4": -30.0} 64 65 for x in df_results.itertuples(): 66 player_list = (str(x.p1_name), str(x.p2_name), str(x.p3_name), str(x.p4_name)) 67 for player in player_list: 68 if player not in df_ratings.columns: 69 last_ratings[player] = 1500.0 70 df_ratings[player] = np.nan 71 df_ratings.loc["initial_rating", player] = 1500.0 72 df_ratings = df_ratings 73 74 # 天鳳計算式 (https://tenhou.net/man/#RATING) 75 rank_list = (x.p1_rank, x.p2_rank, x.p3_rank, x.p4_rank) 76 rating_list = [last_ratings[player] for player in player_list] 77 rating_avg = float(1500.0 if np.mean(rating_list) < 1500.0 else np.mean(rating_list)) 78 79 for i, player in enumerate(player_list): 80 rating = float(rating_list[i]) 81 rank = str(rank_list[i]) 82 83 correction_value: float = (rating_avg - rating) / 40 84 if df_ratings[player].count() >= 400: 85 match_correction = 0.2 86 else: 87 match_correction = 1 - df_ratings[player].count() * 0.002 88 89 new_rating = rating + match_correction * (score_mapping[rank] + correction_value) 90 91 last_ratings[player] = new_rating 92 df_ratings.loc[x.Index, player] = new_rating 93 94 # 間引き(集約オプション) 95 if collection := g.params.collection: 96 ratings = df_ratings[1:] 97 ratings.index = pd.to_datetime(ratings.index) # DatetimeIndexに変換 98 99 match collection: 100 case "daily": 101 ratings = ratings.resample("D").last().ffill() 102 case "monthly": 103 ratings = ratings.resample("ME").last().ffill() 104 case "yearly": 105 ratings = ratings.resample("YE").last().ffill() 106 case "all": 107 ratings = df_ratings.ffill().tail(1) 108 case _: 109 return df_ratings 110 111 ratings.index = ratings.index.astype(str) 112 df_ratings = pd.concat([df_ratings.head(1), ratings]) 113 114 return df_ratings
レーティング集計
Returns:
pd.DataFrame: 集計結果
def
grade_promotion_check(grade_level: int, point: int, rank: int) -> tuple[int, int]:
117def grade_promotion_check(grade_level: int, point: int, rank: int) -> tuple[int, int]: 118 """ 119 昇段チェック 120 121 Args: 122 grade_level (int): 現在のレベル(段位) 123 point (int): 現在の昇段ポイント 124 rank (int): 獲得順位 125 126 Returns: 127 tuple[int, int]: チェック後の昇段ポイント, チェック後のレベル(段位) 128 129 """ 130 tbl_data = g.cfg.badge.grade.table["table"] 131 new_point = point + int(tbl_data[grade_level]["acquisition"][rank - 1]) 132 133 if new_point >= int(tbl_data[grade_level]["point"][1]): # level up 134 grade_level = min(grade_level + 1, len(tbl_data) - 1) 135 new_point = int(tbl_data[grade_level]["point"][0]) # 初期値 136 elif new_point < 0: # level down 137 new_point = int(0) 138 if tbl_data[grade_level]["demote"]: 139 grade_level = max(grade_level - 1, 0) 140 new_point = int(tbl_data[grade_level]["point"][0]) # 初期値 141 142 return (new_point, grade_level)
昇段チェック
Arguments:
- grade_level (int): 現在のレベル(段位)
- point (int): 現在の昇段ポイント
- rank (int): 獲得順位
Returns:
tuple[int, int]: チェック後の昇段ポイント, チェック後のレベル(段位)
def
matrix_table() -> pandas.DataFrame:
146def matrix_table() -> pd.DataFrame: 147 """ 148 対局対戦マトリックス表の作成 149 150 Returns: 151 pd.DataFrame: 集計結果 152 153 """ 154 # データ収集 155 df = g.params.read_data("REPORT_MATRIX_TABLE").set_index("playtime") 156 157 # 結果に含まれるプレイヤーのリスト 158 plist = sorted(list(set(df["p1_name"].tolist() + df["p2_name"].tolist() + df["p3_name"].tolist() + df["p4_name"].tolist()))) 159 160 # 順位テーブルの作成 161 l_data: dict[str, Any] = {} 162 for pname in plist: 163 if g.params.individual: # 個人集計 164 l_name = formatter.name_replace(pname) 165 # プレイヤー指定があるなら対象以外をスキップ 166 if g.params.player_list: 167 if l_name not in g.params.player_list: 168 continue 169 # ゲスト置換 170 if g.params.guest_skip: # ゲストあり 171 l_name = formatter.name_replace(pname, add_mark=True) 172 else: # ゲストなし 173 if pname == g.cfg.member.guest_name: 174 continue 175 else: # チーム集計 176 l_name = pname 177 178 l_data[l_name] = [] 179 for x in df.itertuples(): 180 match pname: 181 case x.p1_name: 182 l_data[l_name] += [x.p1_rank] 183 case x.p2_name: 184 l_data[l_name] += [x.p2_rank] 185 case x.p3_name: 186 l_data[l_name] += [x.p3_rank] 187 case x.p4_name: 188 l_data[l_name] += [x.p4_rank] 189 case _: 190 l_data[l_name] += [None] 191 192 # 規定打数以下を足切り 193 if g.params.stipulated: 194 for pname in list(l_data.keys()): 195 if sum(x is not None for x in l_data[pname]) < g.params.stipulated: 196 l_data.pop(pname) 197 198 rank_df = pd.DataFrame(l_data.values(), columns=list(df.index), index=list(l_data.keys())) 199 200 # 対象リストが0件になった場合は空のデータフレームを返す 201 if rank_df.empty: 202 return rank_df 203 204 # 対局対戦マトリックス表の作成 205 mtx_df = pd.DataFrame(index=list(l_data.keys()), columns=list(l_data.keys()) + ["total"]) 206 sorting_df = pd.DataFrame(index=list(l_data.keys()), columns=["win_per", "count"]) 207 208 for idx1 in range(len(rank_df)): 209 p1 = rank_df.iloc[idx1] 210 t_game_count = 0 211 t_win = 0 212 for idx2 in range(len(rank_df)): 213 p2 = rank_df.iloc[idx2] 214 if p1.name == p2.name: 215 mtx_df.loc[f"{p1.name}", f"{p2.name}"] = "---" 216 else: 217 game_count = len(pd.concat([p1, p2], axis=1).dropna()) 218 win = (p1 < p2).sum() 219 t_game_count += game_count 220 t_win += win 221 222 if game_count: 223 winning_per = str(round(float(win / game_count * 100), 1)) 224 else: 225 winning_per = "--.-" 226 mtx_df.loc[f"{p1.name}", f"{p2.name}"] = f"{win}-{game_count - win} ({winning_per}%)" 227 228 if t_game_count: 229 t_winning_per = str(round(float(t_win / t_game_count * 100), 1)) 230 else: 231 t_winning_per = "--.-" 232 mtx_df.loc[f"{p1.name}", "total"] = f"{t_win}-{t_game_count - t_win} ({t_winning_per}%)" 233 sorting_df.loc[f"{p1.name}", "win_per"] = t_winning_per 234 sorting_df.loc[f"{p1.name}", "count"] = t_game_count 235 236 # 勝率で並び替え 237 sorting_df["win_per"] = pd.to_numeric(sorting_df["win_per"], errors="coerce") 238 sorting_df["count"] = pd.to_numeric(sorting_df["count"], errors="coerce") 239 sorting_df = sorting_df.sort_values(by=["win_per", "count"], ascending=[False, False]) 240 mtx_df = mtx_df.reindex(index=list(sorting_df.index), columns=list(sorting_df.index) + ["total"]) 241 242 return mtx_df
対局対戦マトリックス表の作成
Returns:
pd.DataFrame: 集計結果