libs.utils.converter
libs/utils/converter.py
1""" 2libs/utils/converter.py 3""" 4 5import textwrap 6from typing import TYPE_CHECKING, Optional, Union, cast 7 8import pandas as pd 9from table2ascii import Alignment, PresetStyle, table2ascii 10from tabulate import tabulate 11 12import libs.global_value as g 13from libs.utils import formatter, textutil 14 15if TYPE_CHECKING: 16 from pathlib import Path 17 18 19def save_output( 20 df: pd.DataFrame, 21 kind: str, 22 filename: str, 23 headline: Optional[str] = None, 24 suffix: Optional[str] = None, 25) -> Union["Path", None]: 26 """指定されたフォーマットでdfを保存する 27 28 Args: 29 df (pd.DataFrame): 描写対象データ 30 kind (str): フォーマット 31 filename (str): 保存ファイル名 32 headline (Optional[str], optional): 集計情報(ヘッダコメント). Defaults to None. 33 suffix (Optional[str], optional): 保存ファイル名に追加する文字列. Defaults to None. 34 35 Returns: 36 Path: 保存したファイルパス 37 None: 未知のフォーマットが指定された場合 38 """ 39 40 match kind.lower(): 41 case "csv": 42 data = df.to_csv(index=False) 43 case "text" | "txt": 44 data = df.to_markdown( 45 index=False, 46 tablefmt="outline", 47 floatfmt=formatter.floatfmt_adjust(df), 48 colalign=formatter.column_alignment(df, False), 49 # headersalign=column_alignment(df, True), # ToDo: python-tabulate >= 0.10.0 50 ) 51 case _: 52 return None 53 54 # 保存 55 save_file = textutil.save_file_path(filename, True) 56 if suffix and g.params.get("filename"): 57 save_file = save_file.with_name(f"{save_file.stem}_{suffix}{save_file.suffix}") 58 59 with open(save_file, "w", encoding="utf-8") as writefile: 60 if headline is not None: # ヘッダ書き込み 61 for line in headline.splitlines(): 62 writefile.writelines(f"# {line}\n") 63 writefile.writelines("\n") 64 65 writefile.writelines(data) 66 67 return save_file 68 69 70def df_to_text_table(df: pd.DataFrame, step: int = 40, index: bool = False) -> dict: 71 """DataFrameからテキストテーブルの生成 72 73 Args: 74 df (pd.DataFrame): 対象データ 75 step (int, optional): 分割行. Defaults to 40. 76 index (bool, optional): インデックスを含める. Defaults to False. 77 78 Returns: 79 dict: 生成テーブル 80 """ 81 82 # ヘッダ/位置 83 header: list = [] 84 alignments: list = [] 85 if index: 86 df.reset_index(inplace=True, drop=True) 87 df.index += 1 88 header.append("") 89 for col in df.columns: 90 header.append(col) 91 match col: 92 case "名前" | "プレイヤー名": 93 alignments.append(Alignment.LEFT) 94 case "順位分布": 95 alignments.append(Alignment.LEFT) 96 case _: 97 alignments.append(Alignment.RIGHT) 98 99 # 表データ 100 body: list = [] 101 data: list = [] 102 for row in df.to_dict(orient="records"): 103 data.clear() 104 for k, v in row.items(): 105 match k: 106 case "通算" | "平均" | "平均素点": 107 data.append(f" {v:+.1f}".replace(" -", "▲")) 108 case "平順" | "平均順位": 109 data.append(f"{v:.2f}") 110 case "レート": 111 data.append(f"{v:.1f}") 112 case "順位偏差" | "得点偏差": 113 data.append(f"{v:.0f}") 114 case _: 115 data.append(str(v).replace("nan", "*****")) 116 if index: 117 data.insert(0, "") 118 body.append(data.copy()) 119 120 # 表生成/分割 121 my_style = PresetStyle.plain 122 my_style.heading_row_sep = "-" 123 my_style.heading_row_right_tee = "" 124 my_style.heading_row_left_tee = "" 125 126 table_data: dict = {} 127 for idx, table_body in enumerate(textutil.split_balanced(body, step)): 128 output = table2ascii( 129 header=header, 130 body=table_body, 131 style=my_style, 132 cell_padding=0, 133 first_col_heading=index, 134 alignments=alignments, 135 ) 136 table_data.update({f"{idx}": output}) 137 138 return table_data 139 140 141def df_to_results_details(df: pd.DataFrame) -> dict: 142 """戦績(詳細)データをテキスト変換 143 144 Args: 145 df (pd.DataFrame): 対象データ 146 147 Returns: 148 dict: 整形テキスト 149 """ 150 151 data_list: list = [] 152 for x in df.to_dict(orient="index").values(): 153 work_df = pd.DataFrame({ 154 "東家:": [v for k, v in cast(dict, x).items() if str(k).startswith("東家")], 155 "南家:": [v for k, v in cast(dict, x).items() if str(k).startswith("南家")], 156 "西家:": [v for k, v in cast(dict, x).items() if str(k).startswith("西家")], 157 "北家:": [v for k, v in cast(dict, x).items() if str(k).startswith("北家")], 158 }, index=["name", "rpoint", "rank", "point", "grandslam"]).T 159 160 work_df["rpoint"] = work_df.apply(lambda v: f"<>{v["rpoint"]:8d}点".replace("-", "▲"), axis=1) 161 work_df["point"] = work_df.apply(lambda v: f"(<>{v["point"]:7.1f}pt)".replace("-", "▲"), axis=1) 162 work_df["rank"] = work_df.apply(lambda v: f"{v["rank"]}位", axis=1) 163 data = work_df.to_markdown(tablefmt="tsv", headers=[], floatfmt=formatter.floatfmt_adjust(work_df)).replace("<>", "") 164 165 ret = f"{str(x["日時"]).replace("-", "/")} {x["備考"]}\n" 166 ret += textwrap.indent(data, "\t") + "\n" 167 168 data_list.append(ret) 169 170 return {str(idx): x for idx, x in enumerate(formatter.group_strings(data_list, 2000))} 171 172 173def df_to_results_simple(df: pd.DataFrame) -> dict: 174 """戦績(簡易)データをテキスト変換 175 176 Args: 177 df (pd.DataFrame): 対象データ 178 179 Returns: 180 dict: 整形テキスト 181 """ 182 183 data_list: list = [] 184 for x in df.to_dict(orient="index").values(): 185 vs_guest = "" 186 if x["備考"] != "": 187 vs_guest = f"({g.cfg.setting.guest_mark}) " 188 189 ret = f"\t{vs_guest}{str(x["日時"]).replace("-", "/")} " 190 ret += f"{x["座席"]}\t{x["順位"]}位\t{x["素点"]:8d}点\t({x["ポイント"]:7.1f}pt)\t{x["役満和了"]}".replace("-", "▲") 191 data_list.append(ret) 192 193 return {str(idx): x for idx, x in enumerate(formatter.group_strings(data_list, 2500))} 194 195 196def df_to_ranking(df: pd.DataFrame, title: str, step: int = 40) -> dict: 197 """DataFrameからランキングテーブルを生成 198 199 Args: 200 df (pd.DataFrame): 対象データ 201 title (str): 種別 202 step (int, optional): 分割行. Defaults to 40. 203 204 Returns: 205 dict: 整形テキスト 206 """ 207 208 # 表示内容 209 body: list = [] 210 alignments: list = [] 211 match title: 212 case "ゲーム参加率": 213 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 214 for x in df.itertuples(): 215 body.append([ 216 f"{x.順位}:", 217 x.プレイヤー名, 218 f"{x.ゲーム参加率:>7.2%}", 219 f"({x.ゲーム数:4d}G / {x.集計ゲーム数:4d}G)", 220 ]) 221 case "通算ポイント": 222 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 223 for x in df.itertuples(): 224 body.append([ 225 f"{x.順位}:", 226 x.プレイヤー名, 227 f"{x.通算ポイント:>+8.1f}pt".replace("-", "▲"), 228 f"({x.ゲーム数:4d}G)", 229 ]) 230 case "平均ポイント": 231 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 232 for x in df.itertuples(): 233 body.append([ 234 f"{x.順位}:", 235 x.プレイヤー名, 236 f"{x.平均ポイント:>+8.1f}pt".replace("-", "▲"), 237 f"({x.通算ポイント:>+8.1f}pt / {x.ゲーム数:4d}G)".replace("-", "▲"), 238 ]) 239 case "平均収支": 240 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 241 for x in df.itertuples(): 242 body.append([ 243 f"{x.順位}:", 244 x.プレイヤー名, 245 f"{x.平均収支:>6.0f}点".replace("-", "▲"), 246 f"({x.平均素点:>6.0f}点 / {x.ゲーム数:4d}G)".replace("-", "▲"), 247 ]) 248 case "トップ率": 249 df = df.rename(columns={"1位率": "トップ率", "1位数": "トップ数"}) 250 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 251 for x in df.itertuples(): 252 body.append([ 253 f"{x.順位}:", 254 x.プレイヤー名, 255 f"{x.トップ率:>7.2%}", 256 f"({x.トップ数:3d} / {x.ゲーム数:4d}G)", 257 ]) 258 case "連対率": 259 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 260 for x in df.itertuples(): 261 body.append([ 262 f"{x.順位}:", 263 x.プレイヤー名, 264 f"{x.連対率:>7.2%}", 265 f"({x.連対数:3d} / {x.ゲーム数:4d}G)", 266 ]) 267 case "ラス回避率": 268 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 269 for x in df.itertuples(): 270 body.append([ 271 f"{x.順位}:", 272 x.プレイヤー名, 273 f"{x.ラス回避率:>7.2%}", 274 f"({x.ラス回避数:3d} / {x.ゲーム数:4d}G)", 275 ]) 276 case "トビ率": 277 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 278 for x in df.itertuples(): 279 body.append([ 280 f"{x.順位}:", 281 x.プレイヤー名, 282 f"{x.トビ率:>7.2%}", 283 f"({x.トビ数:3d} / {x.ゲーム数:4d}G)", 284 ]) 285 case "平均順位": 286 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 287 for x in df.itertuples(): 288 body.append([ 289 f"{x.順位}:", 290 x.プレイヤー名, 291 f"{x.平均順位:>4.2f}", 292 f"({x.順位分布})", 293 ]) 294 case "役満和了率": 295 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 296 for x in df.itertuples(): 297 body.append([ 298 f"{x.順位}:", 299 x.プレイヤー名, 300 f"{x.役満和了率:>7.2%}", 301 f"({x.役満和了数:3d} / {x.ゲーム数:4d}G)", 302 ]) 303 case "最大素点": 304 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 305 for x in df.itertuples(): 306 body.append([ 307 f"{x.順位}:", 308 x.プレイヤー名, 309 f"{x.最大素点:>6.0f}点".replace("-", "▲"), 310 f"({x.最大獲得ポイント:>+8.1f}pt)".replace("-", "▲"), 311 ]) 312 case "連続トップ": 313 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 314 for x in df.itertuples(): 315 body.append([ 316 f"{x.順位}:", 317 x.プレイヤー名, 318 f"{x.連続トップ:>2d}連続", 319 f"({x.ゲーム数:4d}G)", 320 ]) 321 case "連続連対": 322 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 323 for x in df.itertuples(): 324 body.append([ 325 f"{x.順位}:", 326 x.プレイヤー名, 327 f"{x.連続連対:>2d}連続", 328 f"({x.ゲーム数:4d}G)", 329 ]) 330 case "連続ラス回避": 331 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 332 for x in df.itertuples(): 333 body.append([ 334 f"{x.順位}:", 335 x.プレイヤー名, 336 f"{x.連続ラス回避:>2d}連続", 337 f"({x.ゲーム数:4d}G)", 338 ]) 339 case _: 340 return {} 341 342 # 整形/分割 343 ret: dict = {} 344 if step: 345 data = textutil.split_balanced(body, step) 346 last_block = len(data) 347 else: 348 last_block = 1 349 350 if last_block == 1: 351 output = table2ascii( 352 body=body, 353 style=PresetStyle.plain, 354 cell_padding=0, 355 first_col_heading=True, 356 alignments=alignments, 357 ) 358 ret.update({title: output}) 359 else: 360 count = 0 361 for x in data: 362 count += 1 363 output = table2ascii( 364 body=x, 365 style=PresetStyle.plain, 366 cell_padding=0, 367 first_col_heading=True, 368 alignments=alignments, 369 ) 370 ret.update({f"{title} ({count}/{last_block})": output}) 371 372 return ret 373 374 375def df_to_remarks(df: pd.DataFrame) -> dict: 376 """DataFrameからメモテーブルを生成 377 378 Args: 379 df (pd.DataFrame): 対象データ 380 381 Returns: 382 dict: 整形テキスト 383 """ 384 385 for col in df.columns: 386 match col: 387 case "日時": 388 df["日時"] = df["日時"].map(lambda x: str(x).replace("-", "/")) 389 case "卓外": 390 df["卓外"] = df["卓外"].map(lambda x: f"{x}pt".replace("-", "▲")) 391 392 if "卓外" in df.columns: 393 df["表示"] = df.apply(lambda x: f"{x["日時"]} {x["内容"]} {x["卓外"]} ({x["名前"]})", axis=1) 394 elif "和了役" in df.columns: 395 df["表示"] = df.apply(lambda x: f"{x["日時"]} {x["和了役"]} ({x["名前"]})", axis=1) 396 else: 397 df["表示"] = df.apply(lambda x: f"{x["日時"]} {x["内容"]} ({x["名前"]})", axis=1) 398 399 tbl = tabulate(df.filter(items=["表示"]).values, showindex=False).splitlines()[1:-1] 400 401 return {"0": "\n".join(tbl)} 402 403 404def df_to_count(df: pd.DataFrame, title: str, indent: int = 0) -> dict: 405 """DataFrameからメモの回数表示を生成 406 407 Args: 408 df (pd.DataFrame): 対象データ 409 title (str): _description_ 410 indent (int, optional): インデント. Defaults to 0. 411 412 Returns: 413 dict: 整形テキスト 414 """ 415 match title: 416 case "役満和了": 417 df["表示"] = df.apply(lambda x: f"{x["和了役"]}: {x["回数"]} 回", axis=1) 418 case "卓外ポイント": 419 df["表示"] = df.apply(lambda x: f"{x["内容"]}: {x["回数"]} 回 ({x["ポイント合計"]:.1f}pt)".replace("-", "▲"), axis=1) 420 case "その他": 421 df["表示"] = df.apply(lambda x: f"{x["内容"]}: {x["回数"]} 回", axis=1) 422 423 tbl = tabulate(df.filter(items=["表示"]).values, showindex=False).splitlines()[1:-1] 424 return {"0": textwrap.indent("\n".join(tbl), "\t" * indent)} 425 426 427def df_to_seat_data(df: pd.DataFrame, indent: int = 0) -> dict: 428 """座席データ生成 429 430 Args: 431 df (pd.DataFrame): 対象データ 432 indent (int, optional): インデント. Defaults to 0. 433 434 Returns: 435 dict: 整形テキスト 436 """ 437 438 # 表示加工 439 df["順位分布(平均順位)"] = df.apply(lambda x: f"{x["順位分布"]} ({x["平均順位"]:.2f})", axis=1) 440 df.drop(columns=["順位分布", "平均順位"], inplace=True) 441 df["席"] = df.apply(lambda x: f"{x["席"]}:", axis=1) 442 if "トビ" in df.columns: 443 df["トビ"] = df.apply(lambda x: f"/ {x["トビ"]:3d}", axis=1) 444 if "役満和了" in df.columns: 445 df["役満和了"] = df.apply(lambda x: f"/ {x["役満和了"]:3d}", axis=1) 446 447 # 448 df = df.filter(items=["席", "順位分布(平均順位)", "トビ", "役満和了"]).rename( 449 columns={ 450 "席": "# 席:", 451 "トビ": "/ トビ", 452 "役満和了": "/ 役満 #" 453 } 454 ) 455 456 tbl = df.to_markdown(tablefmt="tsv", index=False).replace("0.00", "-.--").replace(" \t", "") 457 return {"0": textwrap.indent(tbl, "\t" * indent)}
def
save_output( df: pandas.core.frame.DataFrame, kind: str, filename: str, headline: Optional[str] = None, suffix: Optional[str] = None) -> Optional[pathlib.Path]:
20def save_output( 21 df: pd.DataFrame, 22 kind: str, 23 filename: str, 24 headline: Optional[str] = None, 25 suffix: Optional[str] = None, 26) -> Union["Path", None]: 27 """指定されたフォーマットでdfを保存する 28 29 Args: 30 df (pd.DataFrame): 描写対象データ 31 kind (str): フォーマット 32 filename (str): 保存ファイル名 33 headline (Optional[str], optional): 集計情報(ヘッダコメント). Defaults to None. 34 suffix (Optional[str], optional): 保存ファイル名に追加する文字列. Defaults to None. 35 36 Returns: 37 Path: 保存したファイルパス 38 None: 未知のフォーマットが指定された場合 39 """ 40 41 match kind.lower(): 42 case "csv": 43 data = df.to_csv(index=False) 44 case "text" | "txt": 45 data = df.to_markdown( 46 index=False, 47 tablefmt="outline", 48 floatfmt=formatter.floatfmt_adjust(df), 49 colalign=formatter.column_alignment(df, False), 50 # headersalign=column_alignment(df, True), # ToDo: python-tabulate >= 0.10.0 51 ) 52 case _: 53 return None 54 55 # 保存 56 save_file = textutil.save_file_path(filename, True) 57 if suffix and g.params.get("filename"): 58 save_file = save_file.with_name(f"{save_file.stem}_{suffix}{save_file.suffix}") 59 60 with open(save_file, "w", encoding="utf-8") as writefile: 61 if headline is not None: # ヘッダ書き込み 62 for line in headline.splitlines(): 63 writefile.writelines(f"# {line}\n") 64 writefile.writelines("\n") 65 66 writefile.writelines(data) 67 68 return save_file
指定されたフォーマットでdfを保存する
Arguments:
- df (pd.DataFrame): 描写対象データ
- kind (str): フォーマット
- filename (str): 保存ファイル名
- headline (Optional[str], optional): 集計情報(ヘッダコメント). Defaults to None.
- suffix (Optional[str], optional): 保存ファイル名に追加する文字列. Defaults to None.
Returns:
Path: 保存したファイルパス None: 未知のフォーマットが指定された場合
def
df_to_text_table( df: pandas.core.frame.DataFrame, step: int = 40, index: bool = False) -> dict:
71def df_to_text_table(df: pd.DataFrame, step: int = 40, index: bool = False) -> dict: 72 """DataFrameからテキストテーブルの生成 73 74 Args: 75 df (pd.DataFrame): 対象データ 76 step (int, optional): 分割行. Defaults to 40. 77 index (bool, optional): インデックスを含める. Defaults to False. 78 79 Returns: 80 dict: 生成テーブル 81 """ 82 83 # ヘッダ/位置 84 header: list = [] 85 alignments: list = [] 86 if index: 87 df.reset_index(inplace=True, drop=True) 88 df.index += 1 89 header.append("") 90 for col in df.columns: 91 header.append(col) 92 match col: 93 case "名前" | "プレイヤー名": 94 alignments.append(Alignment.LEFT) 95 case "順位分布": 96 alignments.append(Alignment.LEFT) 97 case _: 98 alignments.append(Alignment.RIGHT) 99 100 # 表データ 101 body: list = [] 102 data: list = [] 103 for row in df.to_dict(orient="records"): 104 data.clear() 105 for k, v in row.items(): 106 match k: 107 case "通算" | "平均" | "平均素点": 108 data.append(f" {v:+.1f}".replace(" -", "▲")) 109 case "平順" | "平均順位": 110 data.append(f"{v:.2f}") 111 case "レート": 112 data.append(f"{v:.1f}") 113 case "順位偏差" | "得点偏差": 114 data.append(f"{v:.0f}") 115 case _: 116 data.append(str(v).replace("nan", "*****")) 117 if index: 118 data.insert(0, "") 119 body.append(data.copy()) 120 121 # 表生成/分割 122 my_style = PresetStyle.plain 123 my_style.heading_row_sep = "-" 124 my_style.heading_row_right_tee = "" 125 my_style.heading_row_left_tee = "" 126 127 table_data: dict = {} 128 for idx, table_body in enumerate(textutil.split_balanced(body, step)): 129 output = table2ascii( 130 header=header, 131 body=table_body, 132 style=my_style, 133 cell_padding=0, 134 first_col_heading=index, 135 alignments=alignments, 136 ) 137 table_data.update({f"{idx}": output}) 138 139 return table_data
DataFrameからテキストテーブルの生成
Arguments:
- df (pd.DataFrame): 対象データ
- step (int, optional): 分割行. Defaults to 40.
- index (bool, optional): インデックスを含める. Defaults to False.
Returns:
dict: 生成テーブル
def
df_to_results_details(df: pandas.core.frame.DataFrame) -> dict:
142def df_to_results_details(df: pd.DataFrame) -> dict: 143 """戦績(詳細)データをテキスト変換 144 145 Args: 146 df (pd.DataFrame): 対象データ 147 148 Returns: 149 dict: 整形テキスト 150 """ 151 152 data_list: list = [] 153 for x in df.to_dict(orient="index").values(): 154 work_df = pd.DataFrame({ 155 "東家:": [v for k, v in cast(dict, x).items() if str(k).startswith("東家")], 156 "南家:": [v for k, v in cast(dict, x).items() if str(k).startswith("南家")], 157 "西家:": [v for k, v in cast(dict, x).items() if str(k).startswith("西家")], 158 "北家:": [v for k, v in cast(dict, x).items() if str(k).startswith("北家")], 159 }, index=["name", "rpoint", "rank", "point", "grandslam"]).T 160 161 work_df["rpoint"] = work_df.apply(lambda v: f"<>{v["rpoint"]:8d}点".replace("-", "▲"), axis=1) 162 work_df["point"] = work_df.apply(lambda v: f"(<>{v["point"]:7.1f}pt)".replace("-", "▲"), axis=1) 163 work_df["rank"] = work_df.apply(lambda v: f"{v["rank"]}位", axis=1) 164 data = work_df.to_markdown(tablefmt="tsv", headers=[], floatfmt=formatter.floatfmt_adjust(work_df)).replace("<>", "") 165 166 ret = f"{str(x["日時"]).replace("-", "/")} {x["備考"]}\n" 167 ret += textwrap.indent(data, "\t") + "\n" 168 169 data_list.append(ret) 170 171 return {str(idx): x for idx, x in enumerate(formatter.group_strings(data_list, 2000))}
戦績(詳細)データをテキスト変換
Arguments:
- df (pd.DataFrame): 対象データ
Returns:
dict: 整形テキスト
def
df_to_results_simple(df: pandas.core.frame.DataFrame) -> dict:
174def df_to_results_simple(df: pd.DataFrame) -> dict: 175 """戦績(簡易)データをテキスト変換 176 177 Args: 178 df (pd.DataFrame): 対象データ 179 180 Returns: 181 dict: 整形テキスト 182 """ 183 184 data_list: list = [] 185 for x in df.to_dict(orient="index").values(): 186 vs_guest = "" 187 if x["備考"] != "": 188 vs_guest = f"({g.cfg.setting.guest_mark}) " 189 190 ret = f"\t{vs_guest}{str(x["日時"]).replace("-", "/")} " 191 ret += f"{x["座席"]}\t{x["順位"]}位\t{x["素点"]:8d}点\t({x["ポイント"]:7.1f}pt)\t{x["役満和了"]}".replace("-", "▲") 192 data_list.append(ret) 193 194 return {str(idx): x for idx, x in enumerate(formatter.group_strings(data_list, 2500))}
戦績(簡易)データをテキスト変換
Arguments:
- df (pd.DataFrame): 対象データ
Returns:
dict: 整形テキスト
def
df_to_ranking(df: pandas.core.frame.DataFrame, title: str, step: int = 40) -> dict:
197def df_to_ranking(df: pd.DataFrame, title: str, step: int = 40) -> dict: 198 """DataFrameからランキングテーブルを生成 199 200 Args: 201 df (pd.DataFrame): 対象データ 202 title (str): 種別 203 step (int, optional): 分割行. Defaults to 40. 204 205 Returns: 206 dict: 整形テキスト 207 """ 208 209 # 表示内容 210 body: list = [] 211 alignments: list = [] 212 match title: 213 case "ゲーム参加率": 214 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 215 for x in df.itertuples(): 216 body.append([ 217 f"{x.順位}:", 218 x.プレイヤー名, 219 f"{x.ゲーム参加率:>7.2%}", 220 f"({x.ゲーム数:4d}G / {x.集計ゲーム数:4d}G)", 221 ]) 222 case "通算ポイント": 223 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 224 for x in df.itertuples(): 225 body.append([ 226 f"{x.順位}:", 227 x.プレイヤー名, 228 f"{x.通算ポイント:>+8.1f}pt".replace("-", "▲"), 229 f"({x.ゲーム数:4d}G)", 230 ]) 231 case "平均ポイント": 232 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 233 for x in df.itertuples(): 234 body.append([ 235 f"{x.順位}:", 236 x.プレイヤー名, 237 f"{x.平均ポイント:>+8.1f}pt".replace("-", "▲"), 238 f"({x.通算ポイント:>+8.1f}pt / {x.ゲーム数:4d}G)".replace("-", "▲"), 239 ]) 240 case "平均収支": 241 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 242 for x in df.itertuples(): 243 body.append([ 244 f"{x.順位}:", 245 x.プレイヤー名, 246 f"{x.平均収支:>6.0f}点".replace("-", "▲"), 247 f"({x.平均素点:>6.0f}点 / {x.ゲーム数:4d}G)".replace("-", "▲"), 248 ]) 249 case "トップ率": 250 df = df.rename(columns={"1位率": "トップ率", "1位数": "トップ数"}) 251 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 252 for x in df.itertuples(): 253 body.append([ 254 f"{x.順位}:", 255 x.プレイヤー名, 256 f"{x.トップ率:>7.2%}", 257 f"({x.トップ数:3d} / {x.ゲーム数:4d}G)", 258 ]) 259 case "連対率": 260 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 261 for x in df.itertuples(): 262 body.append([ 263 f"{x.順位}:", 264 x.プレイヤー名, 265 f"{x.連対率:>7.2%}", 266 f"({x.連対数:3d} / {x.ゲーム数:4d}G)", 267 ]) 268 case "ラス回避率": 269 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 270 for x in df.itertuples(): 271 body.append([ 272 f"{x.順位}:", 273 x.プレイヤー名, 274 f"{x.ラス回避率:>7.2%}", 275 f"({x.ラス回避数:3d} / {x.ゲーム数:4d}G)", 276 ]) 277 case "トビ率": 278 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 279 for x in df.itertuples(): 280 body.append([ 281 f"{x.順位}:", 282 x.プレイヤー名, 283 f"{x.トビ率:>7.2%}", 284 f"({x.トビ数:3d} / {x.ゲーム数:4d}G)", 285 ]) 286 case "平均順位": 287 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 288 for x in df.itertuples(): 289 body.append([ 290 f"{x.順位}:", 291 x.プレイヤー名, 292 f"{x.平均順位:>4.2f}", 293 f"({x.順位分布})", 294 ]) 295 case "役満和了率": 296 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 297 for x in df.itertuples(): 298 body.append([ 299 f"{x.順位}:", 300 x.プレイヤー名, 301 f"{x.役満和了率:>7.2%}", 302 f"({x.役満和了数:3d} / {x.ゲーム数:4d}G)", 303 ]) 304 case "最大素点": 305 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 306 for x in df.itertuples(): 307 body.append([ 308 f"{x.順位}:", 309 x.プレイヤー名, 310 f"{x.最大素点:>6.0f}点".replace("-", "▲"), 311 f"({x.最大獲得ポイント:>+8.1f}pt)".replace("-", "▲"), 312 ]) 313 case "連続トップ": 314 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 315 for x in df.itertuples(): 316 body.append([ 317 f"{x.順位}:", 318 x.プレイヤー名, 319 f"{x.連続トップ:>2d}連続", 320 f"({x.ゲーム数:4d}G)", 321 ]) 322 case "連続連対": 323 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 324 for x in df.itertuples(): 325 body.append([ 326 f"{x.順位}:", 327 x.プレイヤー名, 328 f"{x.連続連対:>2d}連続", 329 f"({x.ゲーム数:4d}G)", 330 ]) 331 case "連続ラス回避": 332 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 333 for x in df.itertuples(): 334 body.append([ 335 f"{x.順位}:", 336 x.プレイヤー名, 337 f"{x.連続ラス回避:>2d}連続", 338 f"({x.ゲーム数:4d}G)", 339 ]) 340 case _: 341 return {} 342 343 # 整形/分割 344 ret: dict = {} 345 if step: 346 data = textutil.split_balanced(body, step) 347 last_block = len(data) 348 else: 349 last_block = 1 350 351 if last_block == 1: 352 output = table2ascii( 353 body=body, 354 style=PresetStyle.plain, 355 cell_padding=0, 356 first_col_heading=True, 357 alignments=alignments, 358 ) 359 ret.update({title: output}) 360 else: 361 count = 0 362 for x in data: 363 count += 1 364 output = table2ascii( 365 body=x, 366 style=PresetStyle.plain, 367 cell_padding=0, 368 first_col_heading=True, 369 alignments=alignments, 370 ) 371 ret.update({f"{title} ({count}/{last_block})": output}) 372 373 return ret
DataFrameからランキングテーブルを生成
Arguments:
- df (pd.DataFrame): 対象データ
- title (str): 種別
- step (int, optional): 分割行. Defaults to 40.
Returns:
dict: 整形テキスト
def
df_to_remarks(df: pandas.core.frame.DataFrame) -> dict:
376def df_to_remarks(df: pd.DataFrame) -> dict: 377 """DataFrameからメモテーブルを生成 378 379 Args: 380 df (pd.DataFrame): 対象データ 381 382 Returns: 383 dict: 整形テキスト 384 """ 385 386 for col in df.columns: 387 match col: 388 case "日時": 389 df["日時"] = df["日時"].map(lambda x: str(x).replace("-", "/")) 390 case "卓外": 391 df["卓外"] = df["卓外"].map(lambda x: f"{x}pt".replace("-", "▲")) 392 393 if "卓外" in df.columns: 394 df["表示"] = df.apply(lambda x: f"{x["日時"]} {x["内容"]} {x["卓外"]} ({x["名前"]})", axis=1) 395 elif "和了役" in df.columns: 396 df["表示"] = df.apply(lambda x: f"{x["日時"]} {x["和了役"]} ({x["名前"]})", axis=1) 397 else: 398 df["表示"] = df.apply(lambda x: f"{x["日時"]} {x["内容"]} ({x["名前"]})", axis=1) 399 400 tbl = tabulate(df.filter(items=["表示"]).values, showindex=False).splitlines()[1:-1] 401 402 return {"0": "\n".join(tbl)}
DataFrameからメモテーブルを生成
Arguments:
- df (pd.DataFrame): 対象データ
Returns:
dict: 整形テキスト
def
df_to_count(df: pandas.core.frame.DataFrame, title: str, indent: int = 0) -> dict:
405def df_to_count(df: pd.DataFrame, title: str, indent: int = 0) -> dict: 406 """DataFrameからメモの回数表示を生成 407 408 Args: 409 df (pd.DataFrame): 対象データ 410 title (str): _description_ 411 indent (int, optional): インデント. Defaults to 0. 412 413 Returns: 414 dict: 整形テキスト 415 """ 416 match title: 417 case "役満和了": 418 df["表示"] = df.apply(lambda x: f"{x["和了役"]}: {x["回数"]} 回", axis=1) 419 case "卓外ポイント": 420 df["表示"] = df.apply(lambda x: f"{x["内容"]}: {x["回数"]} 回 ({x["ポイント合計"]:.1f}pt)".replace("-", "▲"), axis=1) 421 case "その他": 422 df["表示"] = df.apply(lambda x: f"{x["内容"]}: {x["回数"]} 回", axis=1) 423 424 tbl = tabulate(df.filter(items=["表示"]).values, showindex=False).splitlines()[1:-1] 425 return {"0": textwrap.indent("\n".join(tbl), "\t" * indent)}
DataFrameからメモの回数表示を生成
Arguments:
- df (pd.DataFrame): 対象データ
- title (str): _description_
- indent (int, optional): インデント. Defaults to 0.
Returns:
dict: 整形テキスト
def
df_to_seat_data(df: pandas.core.frame.DataFrame, indent: int = 0) -> dict:
428def df_to_seat_data(df: pd.DataFrame, indent: int = 0) -> dict: 429 """座席データ生成 430 431 Args: 432 df (pd.DataFrame): 対象データ 433 indent (int, optional): インデント. Defaults to 0. 434 435 Returns: 436 dict: 整形テキスト 437 """ 438 439 # 表示加工 440 df["順位分布(平均順位)"] = df.apply(lambda x: f"{x["順位分布"]} ({x["平均順位"]:.2f})", axis=1) 441 df.drop(columns=["順位分布", "平均順位"], inplace=True) 442 df["席"] = df.apply(lambda x: f"{x["席"]}:", axis=1) 443 if "トビ" in df.columns: 444 df["トビ"] = df.apply(lambda x: f"/ {x["トビ"]:3d}", axis=1) 445 if "役満和了" in df.columns: 446 df["役満和了"] = df.apply(lambda x: f"/ {x["役満和了"]:3d}", axis=1) 447 448 # 449 df = df.filter(items=["席", "順位分布(平均順位)", "トビ", "役満和了"]).rename( 450 columns={ 451 "席": "# 席:", 452 "トビ": "/ トビ", 453 "役満和了": "/ 役満 #" 454 } 455 ) 456 457 tbl = df.to_markdown(tablefmt="tsv", index=False).replace("0.00", "-.--").replace(" \t", "") 458 return {"0": textwrap.indent(tbl, "\t" * indent)}
座席データ生成
Arguments:
- df (pd.DataFrame): 対象データ
- indent (int, optional): インデント. Defaults to 0.
Returns:
dict: 整形テキスト