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]: 整形テキスト

def df_to_remarks(df: pandas.DataFrame, options: libs.types.StyleOptions) -> 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]: 整形テキスト

def df_to_seat_data(df: pandas.DataFrame, options: libs.types.StyleOptions) -> 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]: 整形テキスト