libs.data.aggregate

lib/database/aggregate.py

  1"""
  2lib/database/aggregate.py
  3"""
  4
  5import logging
  6from typing import cast
  7
  8import numpy as np
  9import pandas as pd
 10
 11import libs.global_value as g
 12from cls.timekit import ExtendedDatetime as ExtDt
 13from cls.types import GameInfoDict
 14from libs.data import loader
 15from libs.utils import formatter
 16
 17
 18def game_info() -> GameInfoDict:
 19    """指定条件を満たすゲーム数のカウント、最初と最後の時刻とコメントを取得
 20
 21    Returns:
 22        GameInfoDict: 取得したデータ
 23    """
 24
 25    # データ収集
 26    df = loader.read_data("game.info.sql")
 27    ret: GameInfoDict = {
 28        "game_count": int(df["count"].to_string(index=False)),
 29        "first_game": ExtDt(),
 30        "last_game": ExtDt(),
 31        "first_comment": None,
 32        "last_comment": None,
 33    }
 34
 35    if cast(int, ret["game_count"]) >= 1:
 36        ret["first_game"] = ExtDt(df["first_game"].to_string(index=False))
 37        ret["last_game"] = ExtDt(df["last_game"].to_string(index=False))
 38        ret["first_comment"] = df["first_comment"].to_string(index=False)
 39        ret["last_comment"] = df["last_comment"].to_string(index=False)
 40
 41    # 規定打数更新
 42    if not g.params.get("stipulated", 0):
 43        match g.params.get("command", ""):
 44            case "results":
 45                g.params["stipulated"] = g.cfg.results.stipulated_calculation(ret["game_count"])
 46            case "graph":
 47                g.params["stipulated"] = g.cfg.graph.stipulated_calculation(ret["game_count"])
 48            case "ranking":
 49                g.params["stipulated"] = g.cfg.ranking.stipulated_calculation(ret["game_count"])
 50            case "report":
 51                g.params["stipulated"] = g.cfg.report.stipulated_calculation(ret["game_count"])
 52
 53    logging.info("return: %s", ret)
 54    return ret
 55
 56
 57def game_summary(filter_items: list | None = None, drop_items: list | None = None) -> pd.DataFrame:
 58    """ゲーム結果をサマライズする
 59
 60    Args:
 61        filter_items (list | None, optional): 抽出するカラム. Defaults to None.
 62        drop_items (list | None, optional): 除外するカラム. Defaults to None.
 63
 64    Returns:
 65        pd.DataFrame: 集計結果
 66    """
 67
 68    # データ収集
 69    df = loader.read_data("summary/total.sql")
 70
 71    if isinstance(filter_items, list):
 72        df = df.filter(items=filter_items)
 73
 74    if isinstance(drop_items, list):
 75        df = df.drop(columns=drop_items)
 76
 77    logging.trace(df)  # type: ignore
 78    return df
 79
 80
 81def remark_count(kind: str):
 82    """メモの内容を種別でカウント
 83
 84    Args:
 85        kind (str): 集計種別
 86
 87    Returns:
 88        pd.DataFrame: 集計結果
 89    """
 90
 91    # データ収集
 92    g.params.update(kind=kind)
 93    df = loader.read_data("summary/remark_count.sql")
 94
 95    if kind == "grandslam":
 96        df = df.filter(items=["name", "matter", "count"])
 97
 98    logging.trace(df)  # type: ignore
 99    return df
100
101
102def game_results() -> pd.DataFrame:
103    """成績を集計する
104
105    Returns:
106        pd.DataFrame: 集計結果
107    """
108
109    # データ収集
110    df = loader.read_data("summary/results.sql")
111
112    # Nullが返ってきたときにobject型になるので型変換
113    df = df.astype({
114        "東家-平均順位": "float", "南家-平均順位": "float", "西家-平均順位": "float", "北家-平均順位": "float",
115        "東家-役満和了": "Int64", "南家-役満和了": "Int64", "西家-役満和了": "Int64", "北家-役満和了": "Int64",
116    }).fillna(0)
117
118    # インデックスの振り直し
119    df = df.reset_index(drop=True)
120    df.index = df.index + 1
121
122    logging.trace(df)  # type: ignore
123    return df
124
125
126# ランキング
127def ranking_record() -> pd.DataFrame:
128    """ランキング集計
129
130    Returns:
131        pd.DataFrame: 集計結果
132    """
133
134    # データ収集
135    gamedata: pd.DataFrame = loader.read_data("ranking/record_count.sql")
136    player_list = gamedata["name"].unique().tolist()
137
138    # 連続順位カウント
139    rank_mask = {
140        "c_top": {1: 1, 2: 0, 3: 0, 4: 0},  # 連続トップ
141        "c_top2": {1: 1, 2: 1, 3: 0, 4: 0},  # 連続連対
142        "c_top3": {1: 1, 2: 1, 3: 1, 4: 0},  # 連続ラス回避
143        "c_low": {1: 0, 2: 1, 3: 1, 4: 1},  # 連続トップなし
144        "c_low2": {1: 0, 2: 0, 3: 1, 4: 1},  # 連続逆連対
145        "c_low4": {1: 0, 2: 0, 3: 0, 4: 1},  # 連続ラス
146    }
147
148    record_df = pd.DataFrame(
149        {
150            "name": player_list,
151            "c_top": [0 for _ in player_list],
152            "c_top2": [0 for _ in player_list],
153            "c_top3": [0 for _ in player_list],
154            "c_low": [0 for _ in player_list],
155            "c_low2": [0 for _ in player_list],
156            "c_low4": [0 for _ in player_list],
157        },
158        index=player_list
159    )
160
161    for key, val in rank_mask.items():
162        for pname in player_list:
163            tmp_df = pd.DataFrame()
164            tmp_df["flg"] = gamedata.query(
165                "name == @pname"
166            )["順位"].replace(val)
167
168            tmp_df[key] = tmp_df["flg"].groupby(
169                (tmp_df["flg"] != tmp_df["flg"].shift()).cumsum()
170            ).cumcount() + 1
171            tmp_df.loc[tmp_df["flg"] == 0, key] = 0
172            max_key = key.replace("c_", "max_")
173            record_df.at[pname, max_key] = int(tmp_df[[key]].max().values[0])
174
175            # 最終値
176            record_df.at[pname, key] = tmp_df[key].iloc[-1]
177            record_df[max_key] = record_df[max_key].fillna(0).copy().astype("int")
178
179    # 最大値/最小値追加
180    if not gamedata.empty:
181        record_df["max_point"] = gamedata["max_point"].iloc[0]
182        record_df["min_point"] = gamedata["min_point"].iloc[0]
183        record_df["max_rpoint"] = gamedata["max_rpoint"].iloc[0]
184        record_df["min_rpoint"] = gamedata["min_rpoint"].iloc[0]
185
186    logging.trace(record_df)  # type: ignore
187    return record_df
188
189
190def calculation_rating() -> pd.DataFrame:
191    """レーティング集計
192
193    Returns:
194        pd.DataFrame: 集計結果
195    """
196
197    # データ収集
198    df_results = loader.read_data("ranking/ratings.sql").set_index("playtime")
199    df_ratings = pd.DataFrame(index=["initial_rating"] + df_results.index.to_list())  # 記録用
200    last_ratings: dict = {}  # 最終値格納用
201
202    # 獲得スコア
203    score_mapping = {"1": 30.0, "2": 10.0, "3": -10.0, "4": -30.0}
204
205    for x in df_results.itertuples():
206        player_list = (x.p1_name, x.p2_name, x.p3_name, x.p4_name)
207        for player in player_list:
208            if player not in df_ratings.columns:
209                last_ratings[player] = 1500.0
210                df_ratings[player] = np.nan
211                df_ratings.loc["initial_rating", player] = 1500.0
212                df_ratings = df_ratings.copy()
213
214        # 天鳳計算式 (https://tenhou.net/man/#RATING)
215        rank_list = (x.p1_rank, x.p2_rank, x.p3_rank, x.p4_rank,)
216        rating_list = [last_ratings[player] for player in player_list]
217        rating_avg = 1500.0 if np.mean(rating_list) < 1500.0 else np.mean(rating_list)
218
219        for i, player in enumerate(player_list):
220            rating = float(rating_list[i])
221            rank = str(rank_list[i])
222
223            correction_value: float = (rating_avg - rating) / 40
224            if df_ratings[player].count() >= 400:
225                match_correction = 0.2
226            else:
227                match_correction = 1 - df_ratings[player].count() * 0.002
228
229            new_rating = rating + match_correction * (score_mapping[rank] + correction_value)
230
231            last_ratings[player] = new_rating
232            df_ratings.loc[x.Index, player] = new_rating
233
234    return df_ratings
235
236
237def grade_promotion_check(grade_level: int, point: int, rank: int) -> tuple[int, int]:
238    """昇段チェック
239
240    Args:
241        grade_level (int): 現在のレベル(段位)
242        point (int): 現在の昇段ポイント
243        rank (int): 獲得順位
244
245    Returns:
246        tuple[int, int]: チェック後の昇段ポイント, チェック後のレベル(段位)
247    """
248
249    tbl_data = g.cfg.badge.grade.table["table"]
250    new_point = point + int(tbl_data[grade_level]["acquisition"][rank - 1])
251
252    if new_point >= int(tbl_data[grade_level]["point"][1]):  # level up
253        grade_level = min(grade_level + 1, len(tbl_data) - 1)
254        new_point = int(tbl_data[grade_level]["point"][0])  # 初期値
255    elif new_point < 0:  # level down
256        new_point = int(0)
257        if tbl_data[grade_level]["demote"]:
258            grade_level = max(grade_level - 1, 0)
259            new_point = int(tbl_data[grade_level]["point"][0])  # 初期値
260
261    return (new_point, grade_level)
262
263
264# レポート
265def matrix_table() -> pd.DataFrame:
266    """対局対戦マトリックス表の作成
267
268    Returns:
269        pd.DataFrame: 集計結果
270    """
271
272    # データ収集
273    df = loader.read_data("report/matrix_table.sql").set_index("playtime")
274
275    # 結果に含まれるプレイヤーのリスト
276    plist = sorted(list(set(
277        df["p1_name"].tolist() + df["p2_name"].tolist() + df["p3_name"].tolist() + df["p4_name"].tolist()
278    )))
279
280    # 順位テーブルの作成
281    l_data: dict = {}
282    for pname in plist:
283        if g.params.get("individual"):  # 個人集計
284            l_name = formatter.name_replace(pname)
285            # プレイヤー指定があるなら対象以外をスキップ
286            if g.params["player_list"]:
287                if l_name not in g.params["player_list"].values():
288                    continue
289            # ゲスト置換
290            if g.params.get("guest_skip"):  # ゲストあり
291                l_name = formatter.name_replace(pname, add_mark=True)
292            else:  # ゲストなし
293                if pname == g.cfg.member.guest_name:
294                    continue
295        else:  # チーム集計
296            l_name = pname
297
298        l_data[l_name] = []
299        for x in df.itertuples():
300            match pname:
301                case x.p1_name:
302                    l_data[l_name] += [x.p1_rank]
303                case x.p2_name:
304                    l_data[l_name] += [x.p2_rank]
305                case x.p3_name:
306                    l_data[l_name] += [x.p3_rank]
307                case x.p4_name:
308                    l_data[l_name] += [x.p4_rank]
309                case _:
310                    l_data[l_name] += [None]
311
312    # 規定打数以下を足切り
313    if g.params["stipulated"]:
314        for pname in list(l_data.keys()):
315            if sum(x is not None for x in l_data[pname]) <= g.params["stipulated"]:
316                l_data.pop(pname)
317
318    rank_df = pd.DataFrame(
319        l_data.values(),
320        columns=list(df.index),
321        index=list(l_data.keys())
322    )
323
324    # 対象リストが0件になった場合は空のデータフレームを返す
325    if rank_df.empty:
326        return rank_df
327
328    # 対局対戦マトリックス表の作成
329    mtx_df = pd.DataFrame(
330        index=list(l_data.keys()),
331        columns=list(l_data.keys()) + ["total"]
332    )
333    sorting_df = pd.DataFrame(
334        index=list(l_data.keys()),
335        columns=["win_per", "count"]
336    )
337
338    for idx1 in range(len(rank_df)):
339        p1 = rank_df.iloc[idx1]
340        t_game_count = 0
341        t_win = 0
342        for idx2 in range(len(rank_df)):
343            p2 = rank_df.iloc[idx2]
344            if p1.name == p2.name:
345                mtx_df.loc[f"{p1.name}", f"{p2.name}"] = "---"
346            else:
347                game_count = len(pd.concat([p1, p2], axis=1).dropna())
348                win = (p1 < p2).sum()
349                t_game_count += game_count
350                t_win += win
351
352                if game_count:
353                    winning_per = str(round(float(win / game_count * 100), 1))
354                else:
355                    winning_per = "--.-"
356                mtx_df.loc[f"{p1.name}", f"{p2.name}"] = f"{win}-{game_count - win} ({winning_per}%)"
357
358        if t_game_count:
359            t_winning_per = str(round(float(t_win / t_game_count * 100), 1))
360        else:
361            t_winning_per = "--.-"
362        mtx_df.loc[f"{p1.name}", "total"] = f"{t_win}-{t_game_count - t_win} ({t_winning_per}%)"
363        sorting_df.loc[f"{p1.name}", "win_per"] = t_winning_per
364        sorting_df.loc[f"{p1.name}", "count"] = t_game_count
365
366    # 勝率で並び替え
367    sorting_df["win_per"] = pd.to_numeric(sorting_df["win_per"], errors="coerce")
368    sorting_df["count"] = pd.to_numeric(sorting_df["count"], errors="coerce")
369    sorting_df = sorting_df.sort_values(by=["win_per", "count"], ascending=[False, False])
370    mtx_df = mtx_df.reindex(
371        index=list(sorting_df.index),
372        columns=list(sorting_df.index) + ["total"]
373    )
374
375    return mtx_df
def game_info() -> cls.types.GameInfoDict:
19def game_info() -> GameInfoDict:
20    """指定条件を満たすゲーム数のカウント、最初と最後の時刻とコメントを取得
21
22    Returns:
23        GameInfoDict: 取得したデータ
24    """
25
26    # データ収集
27    df = loader.read_data("game.info.sql")
28    ret: GameInfoDict = {
29        "game_count": int(df["count"].to_string(index=False)),
30        "first_game": ExtDt(),
31        "last_game": ExtDt(),
32        "first_comment": None,
33        "last_comment": None,
34    }
35
36    if cast(int, ret["game_count"]) >= 1:
37        ret["first_game"] = ExtDt(df["first_game"].to_string(index=False))
38        ret["last_game"] = ExtDt(df["last_game"].to_string(index=False))
39        ret["first_comment"] = df["first_comment"].to_string(index=False)
40        ret["last_comment"] = df["last_comment"].to_string(index=False)
41
42    # 規定打数更新
43    if not g.params.get("stipulated", 0):
44        match g.params.get("command", ""):
45            case "results":
46                g.params["stipulated"] = g.cfg.results.stipulated_calculation(ret["game_count"])
47            case "graph":
48                g.params["stipulated"] = g.cfg.graph.stipulated_calculation(ret["game_count"])
49            case "ranking":
50                g.params["stipulated"] = g.cfg.ranking.stipulated_calculation(ret["game_count"])
51            case "report":
52                g.params["stipulated"] = g.cfg.report.stipulated_calculation(ret["game_count"])
53
54    logging.info("return: %s", ret)
55    return ret

指定条件を満たすゲーム数のカウント、最初と最後の時刻とコメントを取得

Returns:

GameInfoDict: 取得したデータ

def game_summary( filter_items: list | None = None, drop_items: list | None = None) -> pandas.core.frame.DataFrame:
58def game_summary(filter_items: list | None = None, drop_items: list | None = None) -> pd.DataFrame:
59    """ゲーム結果をサマライズする
60
61    Args:
62        filter_items (list | None, optional): 抽出するカラム. Defaults to None.
63        drop_items (list | None, optional): 除外するカラム. Defaults to None.
64
65    Returns:
66        pd.DataFrame: 集計結果
67    """
68
69    # データ収集
70    df = loader.read_data("summary/total.sql")
71
72    if isinstance(filter_items, list):
73        df = df.filter(items=filter_items)
74
75    if isinstance(drop_items, list):
76        df = df.drop(columns=drop_items)
77
78    logging.trace(df)  # type: ignore
79    return df

ゲーム結果をサマライズする

Arguments:
  • filter_items (list | None, optional): 抽出するカラム. Defaults to None.
  • drop_items (list | None, optional): 除外するカラム. Defaults to None.
Returns:

pd.DataFrame: 集計結果

def remark_count(kind: str):
 82def remark_count(kind: str):
 83    """メモの内容を種別でカウント
 84
 85    Args:
 86        kind (str): 集計種別
 87
 88    Returns:
 89        pd.DataFrame: 集計結果
 90    """
 91
 92    # データ収集
 93    g.params.update(kind=kind)
 94    df = loader.read_data("summary/remark_count.sql")
 95
 96    if kind == "grandslam":
 97        df = df.filter(items=["name", "matter", "count"])
 98
 99    logging.trace(df)  # type: ignore
100    return df

メモの内容を種別でカウント

Arguments:
  • kind (str): 集計種別
Returns:

pd.DataFrame: 集計結果

def game_results() -> pandas.core.frame.DataFrame:
103def game_results() -> pd.DataFrame:
104    """成績を集計する
105
106    Returns:
107        pd.DataFrame: 集計結果
108    """
109
110    # データ収集
111    df = loader.read_data("summary/results.sql")
112
113    # Nullが返ってきたときにobject型になるので型変換
114    df = df.astype({
115        "東家-平均順位": "float", "南家-平均順位": "float", "西家-平均順位": "float", "北家-平均順位": "float",
116        "東家-役満和了": "Int64", "南家-役満和了": "Int64", "西家-役満和了": "Int64", "北家-役満和了": "Int64",
117    }).fillna(0)
118
119    # インデックスの振り直し
120    df = df.reset_index(drop=True)
121    df.index = df.index + 1
122
123    logging.trace(df)  # type: ignore
124    return df

成績を集計する

Returns:

pd.DataFrame: 集計結果

def ranking_record() -> pandas.core.frame.DataFrame:
128def ranking_record() -> pd.DataFrame:
129    """ランキング集計
130
131    Returns:
132        pd.DataFrame: 集計結果
133    """
134
135    # データ収集
136    gamedata: pd.DataFrame = loader.read_data("ranking/record_count.sql")
137    player_list = gamedata["name"].unique().tolist()
138
139    # 連続順位カウント
140    rank_mask = {
141        "c_top": {1: 1, 2: 0, 3: 0, 4: 0},  # 連続トップ
142        "c_top2": {1: 1, 2: 1, 3: 0, 4: 0},  # 連続連対
143        "c_top3": {1: 1, 2: 1, 3: 1, 4: 0},  # 連続ラス回避
144        "c_low": {1: 0, 2: 1, 3: 1, 4: 1},  # 連続トップなし
145        "c_low2": {1: 0, 2: 0, 3: 1, 4: 1},  # 連続逆連対
146        "c_low4": {1: 0, 2: 0, 3: 0, 4: 1},  # 連続ラス
147    }
148
149    record_df = pd.DataFrame(
150        {
151            "name": player_list,
152            "c_top": [0 for _ in player_list],
153            "c_top2": [0 for _ in player_list],
154            "c_top3": [0 for _ in player_list],
155            "c_low": [0 for _ in player_list],
156            "c_low2": [0 for _ in player_list],
157            "c_low4": [0 for _ in player_list],
158        },
159        index=player_list
160    )
161
162    for key, val in rank_mask.items():
163        for pname in player_list:
164            tmp_df = pd.DataFrame()
165            tmp_df["flg"] = gamedata.query(
166                "name == @pname"
167            )["順位"].replace(val)
168
169            tmp_df[key] = tmp_df["flg"].groupby(
170                (tmp_df["flg"] != tmp_df["flg"].shift()).cumsum()
171            ).cumcount() + 1
172            tmp_df.loc[tmp_df["flg"] == 0, key] = 0
173            max_key = key.replace("c_", "max_")
174            record_df.at[pname, max_key] = int(tmp_df[[key]].max().values[0])
175
176            # 最終値
177            record_df.at[pname, key] = tmp_df[key].iloc[-1]
178            record_df[max_key] = record_df[max_key].fillna(0).copy().astype("int")
179
180    # 最大値/最小値追加
181    if not gamedata.empty:
182        record_df["max_point"] = gamedata["max_point"].iloc[0]
183        record_df["min_point"] = gamedata["min_point"].iloc[0]
184        record_df["max_rpoint"] = gamedata["max_rpoint"].iloc[0]
185        record_df["min_rpoint"] = gamedata["min_rpoint"].iloc[0]
186
187    logging.trace(record_df)  # type: ignore
188    return record_df

ランキング集計

Returns:

pd.DataFrame: 集計結果

def calculation_rating() -> pandas.core.frame.DataFrame:
191def calculation_rating() -> pd.DataFrame:
192    """レーティング集計
193
194    Returns:
195        pd.DataFrame: 集計結果
196    """
197
198    # データ収集
199    df_results = loader.read_data("ranking/ratings.sql").set_index("playtime")
200    df_ratings = pd.DataFrame(index=["initial_rating"] + df_results.index.to_list())  # 記録用
201    last_ratings: dict = {}  # 最終値格納用
202
203    # 獲得スコア
204    score_mapping = {"1": 30.0, "2": 10.0, "3": -10.0, "4": -30.0}
205
206    for x in df_results.itertuples():
207        player_list = (x.p1_name, x.p2_name, x.p3_name, x.p4_name)
208        for player in player_list:
209            if player not in df_ratings.columns:
210                last_ratings[player] = 1500.0
211                df_ratings[player] = np.nan
212                df_ratings.loc["initial_rating", player] = 1500.0
213                df_ratings = df_ratings.copy()
214
215        # 天鳳計算式 (https://tenhou.net/man/#RATING)
216        rank_list = (x.p1_rank, x.p2_rank, x.p3_rank, x.p4_rank,)
217        rating_list = [last_ratings[player] for player in player_list]
218        rating_avg = 1500.0 if np.mean(rating_list) < 1500.0 else np.mean(rating_list)
219
220        for i, player in enumerate(player_list):
221            rating = float(rating_list[i])
222            rank = str(rank_list[i])
223
224            correction_value: float = (rating_avg - rating) / 40
225            if df_ratings[player].count() >= 400:
226                match_correction = 0.2
227            else:
228                match_correction = 1 - df_ratings[player].count() * 0.002
229
230            new_rating = rating + match_correction * (score_mapping[rank] + correction_value)
231
232            last_ratings[player] = new_rating
233            df_ratings.loc[x.Index, player] = new_rating
234
235    return df_ratings

レーティング集計

Returns:

pd.DataFrame: 集計結果

def grade_promotion_check(grade_level: int, point: int, rank: int) -> tuple[int, int]:
238def grade_promotion_check(grade_level: int, point: int, rank: int) -> tuple[int, int]:
239    """昇段チェック
240
241    Args:
242        grade_level (int): 現在のレベル(段位)
243        point (int): 現在の昇段ポイント
244        rank (int): 獲得順位
245
246    Returns:
247        tuple[int, int]: チェック後の昇段ポイント, チェック後のレベル(段位)
248    """
249
250    tbl_data = g.cfg.badge.grade.table["table"]
251    new_point = point + int(tbl_data[grade_level]["acquisition"][rank - 1])
252
253    if new_point >= int(tbl_data[grade_level]["point"][1]):  # level up
254        grade_level = min(grade_level + 1, len(tbl_data) - 1)
255        new_point = int(tbl_data[grade_level]["point"][0])  # 初期値
256    elif new_point < 0:  # level down
257        new_point = int(0)
258        if tbl_data[grade_level]["demote"]:
259            grade_level = max(grade_level - 1, 0)
260            new_point = int(tbl_data[grade_level]["point"][0])  # 初期値
261
262    return (new_point, grade_level)

昇段チェック

Arguments:
  • grade_level (int): 現在のレベル(段位)
  • point (int): 現在の昇段ポイント
  • rank (int): 獲得順位
Returns:

tuple[int, int]: チェック後の昇段ポイント, チェック後のレベル(段位)

def matrix_table() -> pandas.core.frame.DataFrame:
266def matrix_table() -> pd.DataFrame:
267    """対局対戦マトリックス表の作成
268
269    Returns:
270        pd.DataFrame: 集計結果
271    """
272
273    # データ収集
274    df = loader.read_data("report/matrix_table.sql").set_index("playtime")
275
276    # 結果に含まれるプレイヤーのリスト
277    plist = sorted(list(set(
278        df["p1_name"].tolist() + df["p2_name"].tolist() + df["p3_name"].tolist() + df["p4_name"].tolist()
279    )))
280
281    # 順位テーブルの作成
282    l_data: dict = {}
283    for pname in plist:
284        if g.params.get("individual"):  # 個人集計
285            l_name = formatter.name_replace(pname)
286            # プレイヤー指定があるなら対象以外をスキップ
287            if g.params["player_list"]:
288                if l_name not in g.params["player_list"].values():
289                    continue
290            # ゲスト置換
291            if g.params.get("guest_skip"):  # ゲストあり
292                l_name = formatter.name_replace(pname, add_mark=True)
293            else:  # ゲストなし
294                if pname == g.cfg.member.guest_name:
295                    continue
296        else:  # チーム集計
297            l_name = pname
298
299        l_data[l_name] = []
300        for x in df.itertuples():
301            match pname:
302                case x.p1_name:
303                    l_data[l_name] += [x.p1_rank]
304                case x.p2_name:
305                    l_data[l_name] += [x.p2_rank]
306                case x.p3_name:
307                    l_data[l_name] += [x.p3_rank]
308                case x.p4_name:
309                    l_data[l_name] += [x.p4_rank]
310                case _:
311                    l_data[l_name] += [None]
312
313    # 規定打数以下を足切り
314    if g.params["stipulated"]:
315        for pname in list(l_data.keys()):
316            if sum(x is not None for x in l_data[pname]) <= g.params["stipulated"]:
317                l_data.pop(pname)
318
319    rank_df = pd.DataFrame(
320        l_data.values(),
321        columns=list(df.index),
322        index=list(l_data.keys())
323    )
324
325    # 対象リストが0件になった場合は空のデータフレームを返す
326    if rank_df.empty:
327        return rank_df
328
329    # 対局対戦マトリックス表の作成
330    mtx_df = pd.DataFrame(
331        index=list(l_data.keys()),
332        columns=list(l_data.keys()) + ["total"]
333    )
334    sorting_df = pd.DataFrame(
335        index=list(l_data.keys()),
336        columns=["win_per", "count"]
337    )
338
339    for idx1 in range(len(rank_df)):
340        p1 = rank_df.iloc[idx1]
341        t_game_count = 0
342        t_win = 0
343        for idx2 in range(len(rank_df)):
344            p2 = rank_df.iloc[idx2]
345            if p1.name == p2.name:
346                mtx_df.loc[f"{p1.name}", f"{p2.name}"] = "---"
347            else:
348                game_count = len(pd.concat([p1, p2], axis=1).dropna())
349                win = (p1 < p2).sum()
350                t_game_count += game_count
351                t_win += win
352
353                if game_count:
354                    winning_per = str(round(float(win / game_count * 100), 1))
355                else:
356                    winning_per = "--.-"
357                mtx_df.loc[f"{p1.name}", f"{p2.name}"] = f"{win}-{game_count - win} ({winning_per}%)"
358
359        if t_game_count:
360            t_winning_per = str(round(float(t_win / t_game_count * 100), 1))
361        else:
362            t_winning_per = "--.-"
363        mtx_df.loc[f"{p1.name}", "total"] = f"{t_win}-{t_game_count - t_win} ({t_winning_per}%)"
364        sorting_df.loc[f"{p1.name}", "win_per"] = t_winning_per
365        sorting_df.loc[f"{p1.name}", "count"] = t_game_count
366
367    # 勝率で並び替え
368    sorting_df["win_per"] = pd.to_numeric(sorting_df["win_per"], errors="coerce")
369    sorting_df["count"] = pd.to_numeric(sorting_df["count"], errors="coerce")
370    sorting_df = sorting_df.sort_values(by=["win_per", "count"], ascending=[False, False])
371    mtx_df = mtx_df.reindex(
372        index=list(sorting_df.index),
373        columns=list(sorting_df.index) + ["total"]
374    )
375
376    return mtx_df

対局対戦マトリックス表の作成

Returns:

pd.DataFrame: 集計結果