"""
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 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