tklib.tkvariousdata のソースコード

"""
tkvariousdata.py: さまざまなデータファイル(CSV、Excel)を扱うためのクラスを提供します。

このモジュールは、CSVファイルやExcelファイルからデータを読み込み、
またそれらの形式でデータを書き出すための汎用的なデータハンドリングクラス
`tkVariousData` を含んでいます。
データの前処理、フィルタリング、フォーマット変換などの機能を提供します。

関連リンク: :doc:`tkvariousdata_usage`
"""
import os
import sys
import csv
import re
import numpy as np
import pandas as pd
import openpyxl


from tklib.tkprogvars import tkprog_X_path
from tklib.tkutils import terminate, get_ext
from tklib.tkdatafile import tkDataFile
from tklib.tkcsv import tkCSV
from tklib.tkexcel import tkExcel
import tklib.tkre as tkre


pattern_int   = re.compile(r'^[-+]?[0-9]+$')
pattern_float = re.compile(r'^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$')


[ドキュメント] def find_template(template): """ 指定されたExcelテンプレートファイルのパスを検索します。 カレントディレクトリ、スクリプト実行ディレクトリ、および特定のシステムパス配下を順に探索し、 最初に見つかったテンプレートファイルの絶対パスを返します。 :param template: 検索するテンプレートファイル名または相対パス。 :type template: str :returns: 見つかったテンプレートファイルの絶対パス。見つからない場合は None。 :rtype: str or None """ if os.path.isfile(template): return template script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) candidate = os.path.join(script_dir, template) if os.path.isfile(candidate): return candidate _template = os.path.join(tkprog_X_path, "excel", "template", template) if os.path.isfile(_template): return _template return None
[ドキュメント] class tkVariousData(tkDataFile): """ さまざまなデータファイル形式(CSV、Excel)を扱うためのクラスです。 このクラスは、CSVファイルやExcelファイルからのデータの読み込み、 特定のデータ抽出、そしてCSVやExcelファイルへの書き出し機能を提供します。 `tkDataFile` を継承しており、ファイル操作の基本機能を利用できます。 """ def __init__(self, path = None, mode = 'r', OpenFile = 1, data_only = True, **args): """ tkVariousDataオブジェクトを初期化します。 :param path: 操作対象のファイルパス。 :type path: str, optional :param mode: ファイルオープンモード(例: 'r' 読み込み、'w' 書き込み)。 :type mode: str, optional :param OpenFile: ファイルをすぐに開くかどうかを示すフラグ(このクラスでは主に親クラスに引き継がれます)。 :type OpenFile: int, optional :param data_only: Excelからデータを読み込む際に、数式ではなく計算結果の値のみを読み込むかどうか。 (tkExcelクラスに渡されるオプション) :type data_only: bool, optional :param **args: 親クラスのコンストラクタに渡されるその他のキーワード引数。 """ # super(tkFile, self).__init__(path, mode, **args) self.fp = None self.path = None self.mode = None self.path = self.IfYes(path is not None, path, self.path) self.mode = self.IfYes(mode is not None, mode, self.mode) self.update(**args) self.labelarray = [] self.datalistarray = [] def __del__(self): """ オブジェクトが破棄される際にファイルを閉じます。 """ self.Close() def __str__(self): """ オブジェクトの文字列表現を返します。 :returns: クラスのパスを含む文字列。 :rtype: str """ return self.ClassPath()
[ドキュメント] def Read_minimum_matrix(self, close_fp = False, force_numeric = True, usage = None): """ 指定されたファイルから最小限の行列形式でデータを読み込みます。 ファイルの拡張子(.xlsxまたは.csv)に基づいて適切なハンドラ(tkExcelまたはtkCSV)を使用し、 ヘッダー(labelarray)とデータリスト(datalistarray)を取得します。 :param close_fp: データを読み込んだ後にファイルポインタを閉じるかどうか。 :type close_fp: bool :param force_numeric: 可能な限りデータを数値型に強制変換するかどうか。 :type force_numeric: bool :param usage: エラーメッセージに追加する使用法に関する情報。 :type usage: str, optional :returns: (labelarray, datalistarray) のタプル。 labelarrayはヘッダーのリスト、datalistarrayは列ごとのデータのリスト。 :rtype: tuple of (list, list) """ if(tkre.Search(r'\.xlsx$', self.path, flag ='i')): excel = tkExcel(self.path) self.labelarray, self.datalistarray = excel.Read_minimum_matrix(close_fp = close_fp, force_numeric = force_numeric) elif(tkre.Search(r'\.csv$', self.path, flag ='i')): csv = tkCSV(self.path) self.labelarray, self.datalistarray = csv.Read_minimum_matrix(close_fp = close_fp, force_numeric = force_numeric) else: terminate("Error in tkVariousData.Read_minimum_matrix(): File type [{}] is not supported." .format(self.path), usage = usage) return self.labelarray, self.datalistarray
[ドキュメント] def read_data(self, infile, xlabel = 0, ylabel = 1, xmin = None, xmax = None, usage = None): """ 指定された入力ファイルからデータを読み込み、特定の範囲でフィルタリングします。 `Read_minimum_matrix` を使用してデータを読み込み、`xlabel` と `ylabel` で指定された列を抽出し、 `xmin` と `xmax` の範囲でXデータをフィルタリングして、対応するXとYのデータセットを準備します。 :param infile: 読み込む入力ファイルのパス。 :type infile: str :param xlabel: X軸データとして使用する列のラベルまたはインデックス。 :type xlabel: str or int :param ylabel: Y軸データとして使用する列のラベルまたはインデックス。 :type ylabel: str or int :param xmin: Xデータの最小値。この値より小さいXデータは除外されます。 :type xmin: float or int, optional :param xmax: Xデータの最大値。この値より大きいXデータは除外されます。 :type xmax: float or int, optional :param usage: エラーメッセージに追加する使用法に関する情報。 :type usage: str, optional :returns: (labels, datalist) のタプル。 labelsはヘッダーのリスト、datalistは列ごとのデータのリスト。 :rtype: tuple of (list, list) """ print("") print(f"Read [{infile}]") labels, datalist = self.Read_minimum_matrix(close_fp = True, force_numeric = False, usage = usage) xlabel, xin = self.FindDataArray(xlabel, flag = 'i') ylabel, yin = self.FindDataArray(ylabel, flag = 'i') self.labels = labels self.datalist = datalist self.xlabel = xlabel self.ylabel = ylabel self.ndata_all = len(xin) print("ndata_all=", self.ndata_all) self.x = [] self.y = [] self.included_index = [] for i in range(self.ndata_all): if xmin is not None and xmin > xin[i]: continue if xmax is not None and xmax < xin[i]: continue self.x.append(xin[i]) self.y.append(yin[i]) self.included_index.append(i) self.ndata = len(self.x) self.index = range(self.ndata) return labels, datalist
[ドキュメント] def to_csv(self, outfile, labels, data_list, print_level = 1): """ データとラベルをCSVファイルに書き込みます。 データリストを行ごとに整形し、CSV形式で指定されたファイルに保存します。 浮動小数点数は、その値に応じて適切な書式(指数表記または一般表記)で出力されます。 :param outfile: 出力するCSVファイルのパス。 :type outfile: str :param labels: ヘッダーとして使用するラベルのリスト。 :type labels: list of str :param data_list: 列ごとのデータを含むリストのリスト。 :type data_list: list of list :param print_level: ログ出力のレベル。0の場合、メッセージは出力されません。 :type print_level: int :returns: 書き込みが成功した場合は True、失敗した場合は False。 :rtype: bool """ try: if print_level: print(f"Save to [{outfile}]") f = open(outfile, 'w') except Exception as e: if print_level: print(f"Error: Can not write to [{outfile}] due to error [{e}]") return False fout = csv.writer(f, lineterminator='\n') fout.writerow(labels) for i in range(0, len(data_list[0])): a = [] for j in range(len(data_list)): v = data_list[j][i] if type(v) is float: if abs(v) > 1.0e6: v = f"{v:e}" else: v = f"{v:g}" a.append(v) fout.writerow(a) f.close() return True
[ドキュメント] def reformat_workbook(self, wb): """ Excelワークブックの特定のシートを再フォーマットします。 メインシート(最初のシート)の最初の5列1行目から最大行目までのセルの値をクリアし、 ワークブック内のすべてのシートからグラフを削除します。 :param wb: 再フォーマットする openpyxl.Workbook オブジェクト。 :type wb: openpyxl.Workbook :returns: 再フォーマットされたメインワークシートオブジェクト。 :rtype: openpyxl.worksheet.worksheet.Worksheet """ ws_main = wb.worksheets[0] for col in ws_main.iter_cols(min_col=1, max_col=5, min_row=1, max_row=ws_main.max_row): for cell in col: cell.value = None #wbの全シートからChartを探して削除 for sheet in wb.sheetnames: ws = wb[sheet] for chart in ws._charts: ws._charts.remove(chart) return ws_main
[ドキュメント] def to_excel_from_dataframe(self, outfile, df, template = None, isheet_create = 0, print_level = 1): """ Pandas DataFrameからExcelファイルにデータを書き込みます。 DataFrameの列をラベルとして、値をデータリストとして抽出し、 `to_excel` メソッドを呼び出してExcelファイルに保存します。 :param outfile: 出力するExcelファイルのパス。 :type outfile: str :param df: 書き込むデータを含むPandas DataFrame。 :type df: pandas.DataFrame :param template: 使用するExcelテンプレートファイルのパス。テンプレートがない場合は新規作成されます。 :type template: str, optional :param isheet_create: データシートを作成する位置のインデックス。0は先頭。 :type isheet_create: int, optional :param print_level: ログ出力のレベル。0の場合、メッセージは出力されません。 :type print_level: int, optional :returns: 書き込みが成功した場合は True、失敗した場合は False。 :rtype: bool """ labels = list(df.columns) data_list = df.values.tolist() data_list = list(map(list, zip(*data_list))) return self.to_excel(outfile, labels, data_list, template = template, isheet_create = isheet_create, print_level = print_level)
[ドキュメント] def to_excel(self, outfile, labels, data_list, template = None, isheet_create = 0, print_level = 1): """ データとラベルをExcelファイルに書き込みます。 複数行/列のラベルとデータリストを処理し、テンプレートファイルを使用するかどうかで挙動が変わります。 数値に変換可能な文字列は自動的に変換されます。 テンプレートを使用する場合は、VBA対応の`.xlsm`として保存され、既存のシートを整形・グラフ削除後、 指定位置に新しいシートを作成してデータを書き込みます。 :param outfile: 出力するExcelファイルのパス。 :type outfile: str :param labels: ヘッダーとして使用するラベルのリスト。複数のリストのリストの場合、連結されます。 :type labels: list of str or list of list of str :param data_list: 列ごとのデータを含むリストのリスト。複数のリストのリストの場合、連結されます。 :type data_list: list of list or list of list of list :param template: 使用するExcelテンプレートファイルのパス。テンプレートがない場合は新規作成されます。 :type template: str, optional :param isheet_create: データシートを作成する位置のインデックス。0は先頭。 :type isheet_create: int, optional :param print_level: ログ出力のレベル。0の場合、メッセージは出力されません。 :type print_level: int, optional :returns: 書き込みが成功した場合は True、失敗した場合は False。 :rtype: bool """ t = type(labels[0]) if t is list or t is tuple or isinstance(t, np.ndarray): data_all = [] labels_all = [] for d, l in zip(data_list, labels): data_all.extend(d) labels_all.extend(l) data_list = data_all labels = labels_all else: data_list = data_list.copy() nx = len(data_list) max_ndata = 0 for d in data_list: ndata = len(d) if max_ndata < ndata: max_ndata = ndata for i in range(nx): ndata = len(data_list[i]) if ndata < max_ndata: if type(data_list[i]) is not list: data_list[i] = data_list[i].tolist() else: data_list[i] = data_list[i].copy() data_list = data_list.copy() for j in range(max_ndata - ndata): data_list[i].append(None) for i in range(nx): dl = data_list[i] for j in range(len(dl)): t = type(dl[j]) if t is int or t is float: continue if t is str: if pattern_int.match(dl[j]): dl[j] = int(dl[j]) else: if pattern_float.match(dl[j]): dl[j] = float(dl[j]) _template = None if template: _template = find_template(template) if _template != template: print(f"tkVariousData.to_excel(): Use Excel template [{_template}] to save to [{outfile}]") elif _template is None: print(f"Warning in tkVariousData.to_excel(): Can not find Excel template [{template}]") if _template is not None : if not outfile.lower().endswith(".xlsm"): outfile = os.path.splitext(outfile)[0] + ".xlsm" print(f"Warning in tkVariousData.to_excel(): .xlsm template is given but the output file is not .xlsm.") print(f" Change output file path to [{outfile}]\n") worksheet_name = "data" wb = openpyxl.load_workbook(_template, keep_vba=True) self.reformat_workbook(wb) nworksheets = len(wb.sheetnames) if isheet_create <= 0: ws = wb.create_sheet(title=worksheet_name, index = 0) elif nworksheets + 1 < isheet_create: ws = wb.create_sheet(title=worksheet_name, index = nworksheets + 1) else: ws = wb.create_sheet(title=worksheet_name, index = isheet_create) wb.active = wb.sheetnames.index(worksheet_name) else: wb = openpyxl.Workbook() ws = wb.active for col, label in enumerate(labels, 1): ws.cell(row = 1, column = col, value = label) for col, data_col in enumerate(data_list, 1): for row, data in enumerate(data_col, 2): ws.cell(row = row, column = col, value = data) if _template: wb.active = wb.worksheets[0] try: if print_level: print(f"Save to [{os.path.abspath(outfile)}]") wb.save(outfile) except Exception as e: if print_level: print(f"Error: Can not write to [{outfile}] due to error [{e}]") if not os.path.exists(outfile): if print_level: print("") print(f"Error in tkVariousData.to_excel(): Could not write to [{outfile}].") return False return True
[ドキュメント] def save(self, outfile, labels, data_list, print_level = 1): """ ファイルの拡張子に基づいて、データを適切な形式(CSVまたはExcel)で保存します。 `outfile` の拡張子を判別し、`.xlsx` であれば `to_excel` を、 `.csv` であれば `to_csv` を呼び出してデータを保存します。 サポートされていない拡張子の場合はエラーメッセージを出力します。 :param outfile: 出力するファイルのパス。 :type outfile: str :param labels: ヘッダーとして使用するラベルのリスト。 :type labels: list of str :param data_list: 列ごとのデータを含むリストのリスト。 :type data_list: list of list :param print_level: ログ出力のレベル。0の場合、メッセージは出力されません。 :type print_level: int, optional :returns: 保存が成功した場合は True、失敗した場合は False。 :rtype: bool """ ext = get_ext(outfile).lower() if ext == '.xlsx': return self.to_excel(outfile, labels, data_list, print_level = print_level) elif ext == '.csv': return self.to_csv(outfile, labels, data_list, print_level = print_level) if print_level: print(f"Error in tkVariousData().save(): Invalid extension [{ext}] for [{outfile}]") return False