libs.utils.converter
libs/utils/converter.py
1""" 2libs/utils/converter.py 3""" 4 5import textwrap 6from typing import TYPE_CHECKING, Any, Optional, Union 7 8import pandas as pd 9from table2ascii import Alignment, PresetStyle, table2ascii 10from tabulate import tabulate 11 12import libs.global_value as g 13from libs.types import StyleOptions 14from libs.utils import formatter, textutil 15 16if TYPE_CHECKING: 17 from pathlib import Path 18 19 from libs.types import MessageType 20 21 22def save_output( 23 df: pd.DataFrame, 24 options: StyleOptions, 25 headline: Optional[tuple["MessageType", StyleOptions]] = None, 26 suffix: Optional[str] = None, 27) -> Union["Path", None]: 28 """ 29 指定されたフォーマットでdfを保存する 30 31 Args: 32 df (pd.DataFrame): 保存対象データ 33 options (StyleOptions): 詳細オプション 34 headline (tuple[MessageType, StyleOptions], optional): ヘッダコメント. Defaults to None. 35 suffix (str, optional): 保存ファイル名に追加する文字列. Defaults to None. 36 37 Returns: 38 Path: 保存したファイルパス 39 None: ファイル出力なし 40 41 """ 42 # カラムリネーム 43 options.rename_type = StyleOptions.RenameType.NORMAL 44 df = formatter.df_rename(df, options) 45 if options.transpose: 46 df = df.T 47 48 match options.format_type: 49 case "default": 50 return None 51 case "csv": 52 data = df.to_csv(index=options.show_index) 53 case "txt": 54 data = df.to_markdown( 55 index=options.show_index, 56 tablefmt="outline", 57 floatfmt=formatter.floatfmt_adjust(df, index=options.show_index), 58 colalign=formatter.column_alignment(df, index=options.show_index), 59 headersalign=formatter.column_alignment(df, True), 60 ).replace(" ▲", "▲") 61 62 # 保存 63 save_file = textutil.save_file_path(options.filename, True) 64 if suffix and g.params.filename: 65 save_file = save_file.with_name(f"{save_file.stem}_{suffix}{save_file.suffix}") 66 67 with open(save_file, "w", encoding="utf-8") as writefile: 68 # ヘッダコメント書き込み 69 if headline: 70 headline_data, headline_option = headline 71 if options.key_title: 72 writefile.writelines(f"# 【{headline_option.title}】\n") 73 if isinstance(headline_data, str): 74 for line in headline_data.splitlines(): 75 writefile.writelines(f"# {line}\n") 76 writefile.writelines("\n") 77 78 # 本文書き込み 79 writefile.writelines(data) 80 81 return save_file 82 83 84def df_to_text_table(df: pd.DataFrame, options: StyleOptions, step: int = 40) -> dict[str, str]: 85 """ 86 DataFrameからテキストテーブルの生成 87 88 Args: 89 df (pd.DataFrame): 対象データ 90 options (StyleOptions): 表示フラグ 91 step (int, optional): 分割行. Defaults to 40. 92 93 Returns: 94 dict[str, str]: 生成テーブル 95 96 """ 97 df = formatter.df_rename(df, options) 98 99 # ヘッダ/位置 100 header: list[str] = [] 101 alignments: list[Alignment] = [] 102 if options.show_index: 103 df.reset_index(inplace=True, drop=True) 104 df.index += 1 105 header.append("") 106 for col in df.columns: 107 header.append(col) 108 match col: 109 case "名前" | "プレイヤー名" | "チーム" | "チーム名": 110 alignments.append(Alignment.LEFT) 111 case "順位分布": 112 alignments.append(Alignment.LEFT) 113 case _: 114 alignments.append(Alignment.RIGHT) 115 116 # 表データ 117 body: list[list[Any]] = [] 118 data: list[str] = [] 119 for row in df.to_dict(orient="records"): 120 data.clear() 121 for k, v in row.items(): 122 match k: 123 case "通算" | "平均" | "平均素点": 124 data.append(f" {v:+.1f}".replace(" -", "▲")) 125 case "平順" | "平均順位": 126 data.append(f"{v:.2f}") 127 case "レート": 128 data.append(f"{v:.1f}") 129 case "順位偏差" | "得点偏差": 130 data.append(f"{v:.0f}") 131 case _: 132 data.append(str(v).replace("nan", "*****")) 133 if options.show_index: 134 data.insert(0, "") 135 body.append(data.copy()) 136 137 # 表生成/分割 138 my_style = PresetStyle.plain 139 my_style.heading_row_sep = "-" 140 my_style.heading_row_right_tee = "" 141 my_style.heading_row_left_tee = "" 142 143 table_data: dict[str, str] = {} 144 for idx, table_body in enumerate(textutil.split_balanced(body, step)): 145 output = table2ascii( 146 header=header, 147 body=table_body, 148 style=my_style, 149 cell_padding=0, 150 first_col_heading=options.show_index, 151 alignments=alignments, 152 ) 153 table_data.update({f"{idx}": output}) 154 155 return table_data 156 157 158def df_to_text_table2(df: pd.DataFrame, options: StyleOptions, limit: int = 2000) -> dict[str, str]: 159 """ 160 DataFrameからテキストテーブルの生成(縦横変換) 161 162 Args: 163 df (pd.DataFrame): 対象データ 164 options (StyleOptions): 表示フラグ 165 limit (int, optional): 分割文字数. Defaults to 2000. 166 167 Returns: 168 dict: 生成テーブル 169 170 """ 171 df = formatter.df_rename(df, options) 172 173 # 表生成/分割 174 my_style = PresetStyle.plain 175 my_style.heading_row_sep = "-" 176 my_style.heading_row_right_tee = "" 177 my_style.heading_row_left_tee = "" 178 my_style.heading_col_sep = ": " 179 180 table_data: dict[str, str] = {} 181 start_block: int = 0 182 183 safe_output: str = "" 184 output: str = "" 185 186 for cur_block in range(len(df.columns) + 1): 187 chk_df = df.iloc[:, start_block:cur_block] 188 189 # ヘッダ 190 header: list[str] = chk_df.columns.to_list() 191 if options.show_index: 192 header.insert(0, "") 193 194 # ボディ 195 body: list[list[Any]] = [] 196 data: list[Any] = [] 197 for idx, item in chk_df.iterrows(): 198 data.clear() 199 data.append(idx) 200 data.extend(item.to_list()) 201 body.append(data.copy()) 202 203 output = table2ascii( 204 header=header, 205 body=body, 206 style=my_style, 207 cell_padding=0, 208 alignments=[Alignment.RIGHT] * len(header), 209 first_col_heading=options.show_index, 210 ) 211 212 # 文字数チェック 213 if len(output) < limit: 214 safe_output = output 215 else: 216 table_data.update({f"{cur_block}": safe_output}) 217 start_block = cur_block - 1 218 # 最終ブロック 219 if cur_block <= len(df.columns): 220 table_data.update({f"{cur_block}": output}) 221 222 return table_data 223 224 225def df_to_results_details(df: pd.DataFrame, options: StyleOptions, limit: int = 2000) -> dict[str, str]: 226 """ 227 戦績(詳細)データをテキスト変換 228 229 Args: 230 df (pd.DataFrame): 対象データ 231 options (StyleOptions): 表示フラグ 232 limit (int, optional): 分割文字数. Defaults to 2000. 233 234 Returns: 235 dict[str, str]: 整形テキスト 236 237 """ 238 df = formatter.df_rename(df, options) 239 240 data_list: list[str] = [] 241 game_results: dict[str, dict[str, Any]] = {} 242 243 for x in df.to_dict(orient="index").values(): 244 game_results[x["日時"]] = {"備考": x["備考"]} 245 for seat in ("東家", "南家", "西家", "北家"): 246 game_results[x["日時"]].update( 247 { 248 seat: [ 249 x[f"{seat} 名前"], 250 f"{(x[f'{seat} 素点'])}点".replace("-", "▲"), 251 f"{(x[f'{seat} 順位'])}位", 252 f"({float(x[f'{seat} ポイント']):+.1f}pt)".replace("-", "▲"), 253 x[f"{seat} メモ"], 254 ] 255 } 256 ) 257 258 for k, v in game_results.items(): 259 body = [[" 東家:"] + v["東家"], [" 南家:"] + v["南家"], [" 西家:"] + v["西家"], [" 北家:"] + v["北家"]] 260 output = table2ascii( 261 body=body, 262 style=PresetStyle.plain, 263 cell_padding=0, 264 first_col_heading=True, 265 alignments=[Alignment.LEFT, Alignment.LEFT, Alignment.RIGHT, Alignment.RIGHT, Alignment.RIGHT, Alignment.LEFT], 266 ) 267 data_list.append(f"{k.replace('-', '/')} {v['備考']}\n" + output + "\n") 268 269 return {str(idx): x for idx, x in enumerate(formatter.group_strings(data_list, limit))} 270 271 272def df_to_results_simple(df: pd.DataFrame, options: StyleOptions, limit: int = 2000) -> dict[str, str]: 273 """ 274 戦績(簡易)データをテキスト変換 275 276 Args: 277 df (pd.DataFrame): 対象データ 278 options (StyleOptions): 表示フラグ 279 limit (int, optional): 分割文字数. Defaults to 2000. 280 281 Returns: 282 dict[str, str]: 整形テキスト 283 284 """ 285 df = formatter.df_rename(df, options) 286 287 data_list: list[str] = [] 288 for x in df.to_dict(orient="index").values(): 289 vs_guest = "" 290 if x["備考"] != "": 291 vs_guest = f"({g.cfg.setting.guest_mark}) " 292 293 ret = f" {vs_guest}{str(x['日時']).replace('-', '/')} " 294 ret += f"{x['座席']}\t{x['順位']}位\t{x['素点']:8d}点\t{x['獲得ポイント']:7.1f}pt\t{x['メモ']}".replace("-", "▲") 295 data_list.append(ret) 296 297 return {str(idx): x for idx, x in enumerate(formatter.group_strings(data_list, limit))} 298 299 300def df_to_ranking(df: pd.DataFrame, title: str, step: int = 40) -> dict[str, str]: 301 """ 302 DataFrameからランキングテーブルを生成 303 304 Args: 305 df (pd.DataFrame): 対象データ 306 title (str): 種別 307 step (int, optional): 分割行. Defaults to 40. 308 309 Returns: 310 dict[str, str]: 整形テキスト 311 312 """ 313 # 表示内容 314 body: list[list[Any]] = [] 315 alignments: list[Alignment] = [] 316 match title: 317 case "ゲーム参加率": 318 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 319 for x in df.itertuples(): 320 body.append( 321 [ 322 f"{x.rank}:", 323 x.name, 324 x.participation_rate, 325 f"({x.count}/{x.total_count}G)", 326 ] 327 ) 328 case "通算ポイント": 329 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 330 for x in df.itertuples(): 331 body.append( 332 [ 333 f"{x.rank}:", 334 x.name, 335 x.total_point, 336 f"({x.count}G)", 337 ] 338 ) 339 case "平均ポイント": 340 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 341 for x in df.itertuples(): 342 body.append( 343 [ 344 f"{x.rank}:", 345 x.name, 346 x.avg_point, 347 f"({x.total_point}/{x.count}G)", 348 ] 349 ) 350 case "平均収支": 351 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 352 for x in df.itertuples(): 353 body.append( 354 [ 355 f"{x.rank}:", 356 x.name, 357 x.avg_balance, 358 f"({x.rpoint_avg}/{x.count}G)", 359 ] 360 ) 361 case "トップ率": 362 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 363 for x in df.itertuples(): 364 body.append( 365 [ 366 f"{x.rank}:", 367 x.name, 368 x.rank1_rate, 369 f"({x.rank1}/{x.count}G)", 370 ] 371 ) 372 case "連対率": 373 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 374 for x in df.itertuples(): 375 body.append( 376 [ 377 f"{x.rank}:", 378 x.name, 379 x.top2_rate, 380 f"({x.top2}/{x.count}G)", 381 ] 382 ) 383 case "ラス回避率": 384 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 385 for x in df.itertuples(): 386 body.append( 387 [ 388 f"{x.rank}:", 389 x.name, 390 x.top3_rate, 391 f"({x.top3}/{x.count}G)", 392 ] 393 ) 394 case "トビ率": 395 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 396 for x in df.itertuples(): 397 body.append( 398 [ 399 f"{x.rank}:", 400 x.name, 401 x.flying_rate, 402 f"({x.flying}/{x.count}G)", 403 ] 404 ) 405 case "平均順位": 406 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 407 for x in df.itertuples(): 408 body.append( 409 [ 410 f"{x.rank}:", 411 x.name, 412 f"{x.rank_avg}", 413 f"({x.rank_distr}={x.count})".replace("-", "+"), 414 ] 415 ) 416 case "役満和了率": 417 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 418 for x in df.itertuples(): 419 body.append( 420 [ 421 f"{x.rank}:", 422 x.name, 423 x.yakuman_rate, 424 f"({x.yakuman}/{x.count}G)", 425 ] 426 ) 427 case "最大素点": 428 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 429 for x in df.itertuples(): 430 body.append( 431 [ 432 f"{x.rank}:", 433 x.name, 434 x.rpoint_max, 435 f"({x.point_max})", 436 ] 437 ) 438 case "連続トップ": 439 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.LEFT] 440 for x in df.itertuples(): 441 body.append( 442 [ 443 f"{x.rank}:", 444 x.name, 445 f"{x.top1_max:>2d}連続 / {x.count}G", 446 ] 447 ) 448 case "連続連対": 449 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.LEFT] 450 for x in df.itertuples(): 451 body.append( 452 [ 453 f"{x.rank}:", 454 x.name, 455 f"{x.top2_max:>2d}連続 / {x.count}G", 456 ] 457 ) 458 case "連続ラス回避": 459 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.LEFT] 460 for x in df.itertuples(): 461 body.append( 462 [ 463 f"{x.rank}:", 464 x.name, 465 f"{x.top3_max:>2d}連続 / {x.count}G", 466 ] 467 ) 468 case _: 469 return {} 470 471 # 整形/分割 472 ret: dict[str, str] = {} 473 data: list[list[Any]] = [] 474 if step: 475 data = textutil.split_balanced(body, step) 476 last_block = len(data) 477 else: 478 last_block = 1 479 480 if last_block == 1: 481 output = table2ascii( 482 body=body, 483 style=PresetStyle.plain, 484 cell_padding=0, 485 first_col_heading=True, 486 alignments=alignments, 487 ) 488 ret.update({title: output}) 489 else: 490 count = 0 491 for work_body in data: 492 count += 1 493 output = table2ascii( 494 body=work_body, 495 style=PresetStyle.plain, 496 cell_padding=0, 497 first_col_heading=True, 498 alignments=alignments, 499 ) 500 ret.update({f"{title} ({count}/{last_block})": output}) 501 502 return ret 503 504 505def df_to_remarks(df: pd.DataFrame, options: StyleOptions) -> dict[str, str]: 506 """ 507 DataFrameからメモテーブルを生成 508 509 Args: 510 df (pd.DataFrame): 対象データ 511 options (StyleOptions): 表示フラグ 512 513 Returns: 514 dict[str, str]: 整形テキスト 515 516 """ 517 df = formatter.df_rename(df, options) 518 519 key_name = "名前" if g.params.individual else "チーム" 520 for col in df.columns: 521 match col: 522 case "日時": 523 df["日時"] = df["日時"].map(lambda x: str(x).replace("-", "/")) 524 case "ポイント": 525 df["ポイント"] = df["ポイント"].map(lambda x: f"{x}pt".replace("-", "▲")) 526 527 if "日時" in df.columns: 528 if "ポイント" in df.columns: 529 df["表示"] = df.apply(lambda x: f"{x['日時']} {x['ポイント']} {x['内容']} ({x[key_name]})", axis=1) 530 elif "和了役" in df.columns: 531 df["表示"] = df.apply(lambda x: f"{x['日時']} {x['和了役']} ({x[key_name]})", axis=1) 532 else: 533 df["表示"] = df.apply(lambda x: f"{x['日時']} {x['内容']} ({x[key_name]})", axis=1) 534 elif "回数" in df.columns: 535 if "ポイント" in df.columns: 536 df["表示"] = df.apply(lambda x: f"{x['内容']}: {x['回数']} 回 ({x['ポイント合計']:.1f}pt)".replace("-", "▲"), axis=1) 537 elif "和了役" in df.columns: 538 df["表示"] = df.apply(lambda x: f"{x['和了役']}: {x['回数']} 回", axis=1) 539 else: 540 df["表示"] = df.apply(lambda x: f"{x['内容']}: {x['回数']} 回", axis=1) 541 542 tbl = tabulate(df.filter(items=["表示"]).values, showindex=False).splitlines()[1:-1] 543 return {"0": textwrap.indent("\n".join(tbl), "\t" * options.indent)} 544 545 546def df_to_seat_data(df: pd.DataFrame, options: StyleOptions) -> dict[str, str]: 547 """ 548 座席データ生成 549 550 Args: 551 df (pd.DataFrame): 対象データ 552 options (StyleOptions): 表示フラグ 553 554 Returns: 555 dict[str, str]: 整形テキスト 556 557 """ 558 df = formatter.df_rename(df, options) 559 560 # 表示加工 561 df["順位分布(平均順位)"] = df.apply(lambda x: f"{x['順位分布']} ({x['平均順位']})", axis=1) 562 df.drop(columns=["順位分布", "平均順位"], inplace=True) 563 df["席"] = df.apply(lambda x: f"{x['席']}:", axis=1) 564 if "トビ" in df.columns: 565 df["トビ"] = df.apply(lambda x: f"/ {x['トビ']:3d}", axis=1) 566 if "役満和了" in df.columns: 567 df["役満和了"] = df.apply(lambda x: f"/ {x['役満和了']:3d}", axis=1) 568 569 # 570 df = df.filter(items=["席", "順位分布(平均順位)", "トビ", "役満和了"]).rename(columns={"席": "# 席:", "トビ": "/ トビ", "役満和了": "/ 役満 #"}) 571 572 tbl = df.to_markdown(tablefmt="tsv", index=False).replace("0.00", "-.--").replace(" \t", "") 573 return {"0": textwrap.indent(tbl, "\t" * options.indent)}
def
save_output( df: pandas.DataFrame, options: libs.types.StyleOptions, headline: tuple[None | str | pathlib.Path | pandas.DataFrame, libs.types.StyleOptions] | None = None, suffix: str | None = None) -> pathlib.Path | None:
23def save_output( 24 df: pd.DataFrame, 25 options: StyleOptions, 26 headline: Optional[tuple["MessageType", StyleOptions]] = None, 27 suffix: Optional[str] = None, 28) -> Union["Path", None]: 29 """ 30 指定されたフォーマットでdfを保存する 31 32 Args: 33 df (pd.DataFrame): 保存対象データ 34 options (StyleOptions): 詳細オプション 35 headline (tuple[MessageType, StyleOptions], optional): ヘッダコメント. Defaults to None. 36 suffix (str, optional): 保存ファイル名に追加する文字列. Defaults to None. 37 38 Returns: 39 Path: 保存したファイルパス 40 None: ファイル出力なし 41 42 """ 43 # カラムリネーム 44 options.rename_type = StyleOptions.RenameType.NORMAL 45 df = formatter.df_rename(df, options) 46 if options.transpose: 47 df = df.T 48 49 match options.format_type: 50 case "default": 51 return None 52 case "csv": 53 data = df.to_csv(index=options.show_index) 54 case "txt": 55 data = df.to_markdown( 56 index=options.show_index, 57 tablefmt="outline", 58 floatfmt=formatter.floatfmt_adjust(df, index=options.show_index), 59 colalign=formatter.column_alignment(df, index=options.show_index), 60 headersalign=formatter.column_alignment(df, True), 61 ).replace(" ▲", "▲") 62 63 # 保存 64 save_file = textutil.save_file_path(options.filename, True) 65 if suffix and g.params.filename: 66 save_file = save_file.with_name(f"{save_file.stem}_{suffix}{save_file.suffix}") 67 68 with open(save_file, "w", encoding="utf-8") as writefile: 69 # ヘッダコメント書き込み 70 if headline: 71 headline_data, headline_option = headline 72 if options.key_title: 73 writefile.writelines(f"# 【{headline_option.title}】\n") 74 if isinstance(headline_data, str): 75 for line in headline_data.splitlines(): 76 writefile.writelines(f"# {line}\n") 77 writefile.writelines("\n") 78 79 # 本文書き込み 80 writefile.writelines(data) 81 82 return save_file
指定されたフォーマットでdfを保存する
Arguments:
- df (pd.DataFrame): 保存対象データ
- options (StyleOptions): 詳細オプション
- headline (tuple[MessageType, StyleOptions], optional): ヘッダコメント. Defaults to None.
- suffix (str, optional): 保存ファイル名に追加する文字列. Defaults to None.
Returns:
Path: 保存したファイルパス None: ファイル出力なし
def
df_to_text_table( df: pandas.DataFrame, options: libs.types.StyleOptions, step: int = 40) -> dict[str, str]:
85def df_to_text_table(df: pd.DataFrame, options: StyleOptions, step: int = 40) -> dict[str, str]: 86 """ 87 DataFrameからテキストテーブルの生成 88 89 Args: 90 df (pd.DataFrame): 対象データ 91 options (StyleOptions): 表示フラグ 92 step (int, optional): 分割行. Defaults to 40. 93 94 Returns: 95 dict[str, str]: 生成テーブル 96 97 """ 98 df = formatter.df_rename(df, options) 99 100 # ヘッダ/位置 101 header: list[str] = [] 102 alignments: list[Alignment] = [] 103 if options.show_index: 104 df.reset_index(inplace=True, drop=True) 105 df.index += 1 106 header.append("") 107 for col in df.columns: 108 header.append(col) 109 match col: 110 case "名前" | "プレイヤー名" | "チーム" | "チーム名": 111 alignments.append(Alignment.LEFT) 112 case "順位分布": 113 alignments.append(Alignment.LEFT) 114 case _: 115 alignments.append(Alignment.RIGHT) 116 117 # 表データ 118 body: list[list[Any]] = [] 119 data: list[str] = [] 120 for row in df.to_dict(orient="records"): 121 data.clear() 122 for k, v in row.items(): 123 match k: 124 case "通算" | "平均" | "平均素点": 125 data.append(f" {v:+.1f}".replace(" -", "▲")) 126 case "平順" | "平均順位": 127 data.append(f"{v:.2f}") 128 case "レート": 129 data.append(f"{v:.1f}") 130 case "順位偏差" | "得点偏差": 131 data.append(f"{v:.0f}") 132 case _: 133 data.append(str(v).replace("nan", "*****")) 134 if options.show_index: 135 data.insert(0, "") 136 body.append(data.copy()) 137 138 # 表生成/分割 139 my_style = PresetStyle.plain 140 my_style.heading_row_sep = "-" 141 my_style.heading_row_right_tee = "" 142 my_style.heading_row_left_tee = "" 143 144 table_data: dict[str, str] = {} 145 for idx, table_body in enumerate(textutil.split_balanced(body, step)): 146 output = table2ascii( 147 header=header, 148 body=table_body, 149 style=my_style, 150 cell_padding=0, 151 first_col_heading=options.show_index, 152 alignments=alignments, 153 ) 154 table_data.update({f"{idx}": output}) 155 156 return table_data
DataFrameからテキストテーブルの生成
Arguments:
- df (pd.DataFrame): 対象データ
- options (StyleOptions): 表示フラグ
- step (int, optional): 分割行. Defaults to 40.
Returns:
dict[str, str]: 生成テーブル
def
df_to_text_table2( df: pandas.DataFrame, options: libs.types.StyleOptions, limit: int = 2000) -> dict[str, str]:
159def df_to_text_table2(df: pd.DataFrame, options: StyleOptions, limit: int = 2000) -> dict[str, str]: 160 """ 161 DataFrameからテキストテーブルの生成(縦横変換) 162 163 Args: 164 df (pd.DataFrame): 対象データ 165 options (StyleOptions): 表示フラグ 166 limit (int, optional): 分割文字数. Defaults to 2000. 167 168 Returns: 169 dict: 生成テーブル 170 171 """ 172 df = formatter.df_rename(df, options) 173 174 # 表生成/分割 175 my_style = PresetStyle.plain 176 my_style.heading_row_sep = "-" 177 my_style.heading_row_right_tee = "" 178 my_style.heading_row_left_tee = "" 179 my_style.heading_col_sep = ": " 180 181 table_data: dict[str, str] = {} 182 start_block: int = 0 183 184 safe_output: str = "" 185 output: str = "" 186 187 for cur_block in range(len(df.columns) + 1): 188 chk_df = df.iloc[:, start_block:cur_block] 189 190 # ヘッダ 191 header: list[str] = chk_df.columns.to_list() 192 if options.show_index: 193 header.insert(0, "") 194 195 # ボディ 196 body: list[list[Any]] = [] 197 data: list[Any] = [] 198 for idx, item in chk_df.iterrows(): 199 data.clear() 200 data.append(idx) 201 data.extend(item.to_list()) 202 body.append(data.copy()) 203 204 output = table2ascii( 205 header=header, 206 body=body, 207 style=my_style, 208 cell_padding=0, 209 alignments=[Alignment.RIGHT] * len(header), 210 first_col_heading=options.show_index, 211 ) 212 213 # 文字数チェック 214 if len(output) < limit: 215 safe_output = output 216 else: 217 table_data.update({f"{cur_block}": safe_output}) 218 start_block = cur_block - 1 219 # 最終ブロック 220 if cur_block <= len(df.columns): 221 table_data.update({f"{cur_block}": output}) 222 223 return table_data
DataFrameからテキストテーブルの生成(縦横変換)
Arguments:
- df (pd.DataFrame): 対象データ
- options (StyleOptions): 表示フラグ
- limit (int, optional): 分割文字数. Defaults to 2000.
Returns:
dict: 生成テーブル
def
df_to_results_details( df: pandas.DataFrame, options: libs.types.StyleOptions, limit: int = 2000) -> dict[str, str]:
226def df_to_results_details(df: pd.DataFrame, options: StyleOptions, limit: int = 2000) -> dict[str, str]: 227 """ 228 戦績(詳細)データをテキスト変換 229 230 Args: 231 df (pd.DataFrame): 対象データ 232 options (StyleOptions): 表示フラグ 233 limit (int, optional): 分割文字数. Defaults to 2000. 234 235 Returns: 236 dict[str, str]: 整形テキスト 237 238 """ 239 df = formatter.df_rename(df, options) 240 241 data_list: list[str] = [] 242 game_results: dict[str, dict[str, Any]] = {} 243 244 for x in df.to_dict(orient="index").values(): 245 game_results[x["日時"]] = {"備考": x["備考"]} 246 for seat in ("東家", "南家", "西家", "北家"): 247 game_results[x["日時"]].update( 248 { 249 seat: [ 250 x[f"{seat} 名前"], 251 f"{(x[f'{seat} 素点'])}点".replace("-", "▲"), 252 f"{(x[f'{seat} 順位'])}位", 253 f"({float(x[f'{seat} ポイント']):+.1f}pt)".replace("-", "▲"), 254 x[f"{seat} メモ"], 255 ] 256 } 257 ) 258 259 for k, v in game_results.items(): 260 body = [[" 東家:"] + v["東家"], [" 南家:"] + v["南家"], [" 西家:"] + v["西家"], [" 北家:"] + v["北家"]] 261 output = table2ascii( 262 body=body, 263 style=PresetStyle.plain, 264 cell_padding=0, 265 first_col_heading=True, 266 alignments=[Alignment.LEFT, Alignment.LEFT, Alignment.RIGHT, Alignment.RIGHT, Alignment.RIGHT, Alignment.LEFT], 267 ) 268 data_list.append(f"{k.replace('-', '/')} {v['備考']}\n" + output + "\n") 269 270 return {str(idx): x for idx, x in enumerate(formatter.group_strings(data_list, limit))}
戦績(詳細)データをテキスト変換
Arguments:
- df (pd.DataFrame): 対象データ
- options (StyleOptions): 表示フラグ
- limit (int, optional): 分割文字数. Defaults to 2000.
Returns:
dict[str, str]: 整形テキスト
def
df_to_results_simple( df: pandas.DataFrame, options: libs.types.StyleOptions, limit: int = 2000) -> dict[str, str]:
273def df_to_results_simple(df: pd.DataFrame, options: StyleOptions, limit: int = 2000) -> dict[str, str]: 274 """ 275 戦績(簡易)データをテキスト変換 276 277 Args: 278 df (pd.DataFrame): 対象データ 279 options (StyleOptions): 表示フラグ 280 limit (int, optional): 分割文字数. Defaults to 2000. 281 282 Returns: 283 dict[str, str]: 整形テキスト 284 285 """ 286 df = formatter.df_rename(df, options) 287 288 data_list: list[str] = [] 289 for x in df.to_dict(orient="index").values(): 290 vs_guest = "" 291 if x["備考"] != "": 292 vs_guest = f"({g.cfg.setting.guest_mark}) " 293 294 ret = f" {vs_guest}{str(x['日時']).replace('-', '/')} " 295 ret += f"{x['座席']}\t{x['順位']}位\t{x['素点']:8d}点\t{x['獲得ポイント']:7.1f}pt\t{x['メモ']}".replace("-", "▲") 296 data_list.append(ret) 297 298 return {str(idx): x for idx, x in enumerate(formatter.group_strings(data_list, limit))}
戦績(簡易)データをテキスト変換
Arguments:
- df (pd.DataFrame): 対象データ
- options (StyleOptions): 表示フラグ
- limit (int, optional): 分割文字数. Defaults to 2000.
Returns:
dict[str, str]: 整形テキスト
def
df_to_ranking(df: pandas.DataFrame, title: str, step: int = 40) -> dict[str, str]:
301def df_to_ranking(df: pd.DataFrame, title: str, step: int = 40) -> dict[str, str]: 302 """ 303 DataFrameからランキングテーブルを生成 304 305 Args: 306 df (pd.DataFrame): 対象データ 307 title (str): 種別 308 step (int, optional): 分割行. Defaults to 40. 309 310 Returns: 311 dict[str, str]: 整形テキスト 312 313 """ 314 # 表示内容 315 body: list[list[Any]] = [] 316 alignments: list[Alignment] = [] 317 match title: 318 case "ゲーム参加率": 319 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 320 for x in df.itertuples(): 321 body.append( 322 [ 323 f"{x.rank}:", 324 x.name, 325 x.participation_rate, 326 f"({x.count}/{x.total_count}G)", 327 ] 328 ) 329 case "通算ポイント": 330 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 331 for x in df.itertuples(): 332 body.append( 333 [ 334 f"{x.rank}:", 335 x.name, 336 x.total_point, 337 f"({x.count}G)", 338 ] 339 ) 340 case "平均ポイント": 341 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 342 for x in df.itertuples(): 343 body.append( 344 [ 345 f"{x.rank}:", 346 x.name, 347 x.avg_point, 348 f"({x.total_point}/{x.count}G)", 349 ] 350 ) 351 case "平均収支": 352 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 353 for x in df.itertuples(): 354 body.append( 355 [ 356 f"{x.rank}:", 357 x.name, 358 x.avg_balance, 359 f"({x.rpoint_avg}/{x.count}G)", 360 ] 361 ) 362 case "トップ率": 363 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 364 for x in df.itertuples(): 365 body.append( 366 [ 367 f"{x.rank}:", 368 x.name, 369 x.rank1_rate, 370 f"({x.rank1}/{x.count}G)", 371 ] 372 ) 373 case "連対率": 374 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 375 for x in df.itertuples(): 376 body.append( 377 [ 378 f"{x.rank}:", 379 x.name, 380 x.top2_rate, 381 f"({x.top2}/{x.count}G)", 382 ] 383 ) 384 case "ラス回避率": 385 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 386 for x in df.itertuples(): 387 body.append( 388 [ 389 f"{x.rank}:", 390 x.name, 391 x.top3_rate, 392 f"({x.top3}/{x.count}G)", 393 ] 394 ) 395 case "トビ率": 396 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 397 for x in df.itertuples(): 398 body.append( 399 [ 400 f"{x.rank}:", 401 x.name, 402 x.flying_rate, 403 f"({x.flying}/{x.count}G)", 404 ] 405 ) 406 case "平均順位": 407 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 408 for x in df.itertuples(): 409 body.append( 410 [ 411 f"{x.rank}:", 412 x.name, 413 f"{x.rank_avg}", 414 f"({x.rank_distr}={x.count})".replace("-", "+"), 415 ] 416 ) 417 case "役満和了率": 418 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 419 for x in df.itertuples(): 420 body.append( 421 [ 422 f"{x.rank}:", 423 x.name, 424 x.yakuman_rate, 425 f"({x.yakuman}/{x.count}G)", 426 ] 427 ) 428 case "最大素点": 429 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.RIGHT, Alignment.LEFT] 430 for x in df.itertuples(): 431 body.append( 432 [ 433 f"{x.rank}:", 434 x.name, 435 x.rpoint_max, 436 f"({x.point_max})", 437 ] 438 ) 439 case "連続トップ": 440 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.LEFT] 441 for x in df.itertuples(): 442 body.append( 443 [ 444 f"{x.rank}:", 445 x.name, 446 f"{x.top1_max:>2d}連続 / {x.count}G", 447 ] 448 ) 449 case "連続連対": 450 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.LEFT] 451 for x in df.itertuples(): 452 body.append( 453 [ 454 f"{x.rank}:", 455 x.name, 456 f"{x.top2_max:>2d}連続 / {x.count}G", 457 ] 458 ) 459 case "連続ラス回避": 460 alignments = [Alignment.RIGHT, Alignment.LEFT, Alignment.LEFT] 461 for x in df.itertuples(): 462 body.append( 463 [ 464 f"{x.rank}:", 465 x.name, 466 f"{x.top3_max:>2d}連続 / {x.count}G", 467 ] 468 ) 469 case _: 470 return {} 471 472 # 整形/分割 473 ret: dict[str, str] = {} 474 data: list[list[Any]] = [] 475 if step: 476 data = textutil.split_balanced(body, step) 477 last_block = len(data) 478 else: 479 last_block = 1 480 481 if last_block == 1: 482 output = table2ascii( 483 body=body, 484 style=PresetStyle.plain, 485 cell_padding=0, 486 first_col_heading=True, 487 alignments=alignments, 488 ) 489 ret.update({title: output}) 490 else: 491 count = 0 492 for work_body in data: 493 count += 1 494 output = table2ascii( 495 body=work_body, 496 style=PresetStyle.plain, 497 cell_padding=0, 498 first_col_heading=True, 499 alignments=alignments, 500 ) 501 ret.update({f"{title} ({count}/{last_block})": output}) 502 503 return ret
DataFrameからランキングテーブルを生成
Arguments:
- df (pd.DataFrame): 対象データ
- title (str): 種別
- step (int, optional): 分割行. Defaults to 40.
Returns:
dict[str, str]: 整形テキスト
506def df_to_remarks(df: pd.DataFrame, options: StyleOptions) -> dict[str, str]: 507 """ 508 DataFrameからメモテーブルを生成 509 510 Args: 511 df (pd.DataFrame): 対象データ 512 options (StyleOptions): 表示フラグ 513 514 Returns: 515 dict[str, str]: 整形テキスト 516 517 """ 518 df = formatter.df_rename(df, options) 519 520 key_name = "名前" if g.params.individual else "チーム" 521 for col in df.columns: 522 match col: 523 case "日時": 524 df["日時"] = df["日時"].map(lambda x: str(x).replace("-", "/")) 525 case "ポイント": 526 df["ポイント"] = df["ポイント"].map(lambda x: f"{x}pt".replace("-", "▲")) 527 528 if "日時" in df.columns: 529 if "ポイント" in df.columns: 530 df["表示"] = df.apply(lambda x: f"{x['日時']} {x['ポイント']} {x['内容']} ({x[key_name]})", axis=1) 531 elif "和了役" in df.columns: 532 df["表示"] = df.apply(lambda x: f"{x['日時']} {x['和了役']} ({x[key_name]})", axis=1) 533 else: 534 df["表示"] = df.apply(lambda x: f"{x['日時']} {x['内容']} ({x[key_name]})", axis=1) 535 elif "回数" in df.columns: 536 if "ポイント" in df.columns: 537 df["表示"] = df.apply(lambda x: f"{x['内容']}: {x['回数']} 回 ({x['ポイント合計']:.1f}pt)".replace("-", "▲"), axis=1) 538 elif "和了役" in df.columns: 539 df["表示"] = df.apply(lambda x: f"{x['和了役']}: {x['回数']} 回", axis=1) 540 else: 541 df["表示"] = df.apply(lambda x: f"{x['内容']}: {x['回数']} 回", axis=1) 542 543 tbl = tabulate(df.filter(items=["表示"]).values, showindex=False).splitlines()[1:-1] 544 return {"0": textwrap.indent("\n".join(tbl), "\t" * options.indent)}
DataFrameからメモテーブルを生成
Arguments:
- df (pd.DataFrame): 対象データ
- options (StyleOptions): 表示フラグ
Returns:
dict[str, str]: 整形テキスト
547def df_to_seat_data(df: pd.DataFrame, options: StyleOptions) -> dict[str, str]: 548 """ 549 座席データ生成 550 551 Args: 552 df (pd.DataFrame): 対象データ 553 options (StyleOptions): 表示フラグ 554 555 Returns: 556 dict[str, str]: 整形テキスト 557 558 """ 559 df = formatter.df_rename(df, options) 560 561 # 表示加工 562 df["順位分布(平均順位)"] = df.apply(lambda x: f"{x['順位分布']} ({x['平均順位']})", axis=1) 563 df.drop(columns=["順位分布", "平均順位"], inplace=True) 564 df["席"] = df.apply(lambda x: f"{x['席']}:", axis=1) 565 if "トビ" in df.columns: 566 df["トビ"] = df.apply(lambda x: f"/ {x['トビ']:3d}", axis=1) 567 if "役満和了" in df.columns: 568 df["役満和了"] = df.apply(lambda x: f"/ {x['役満和了']:3d}", axis=1) 569 570 # 571 df = df.filter(items=["席", "順位分布(平均順位)", "トビ", "役満和了"]).rename(columns={"席": "# 席:", "トビ": "/ トビ", "役満和了": "/ 役満 #"}) 572 573 tbl = df.to_markdown(tablefmt="tsv", index=False).replace("0.00", "-.--").replace(" \t", "") 574 return {"0": textwrap.indent(tbl, "\t" * options.indent)}
座席データ生成
Arguments:
- df (pd.DataFrame): 対象データ
- options (StyleOptions): 表示フラグ
Returns:
dict[str, str]: 整形テキスト