@hurutoriya さんが、先日以下の記事を投稿していました。
その後ツイッター上でやりとりしているうちにこんな話がありました。
ご指摘ありがとうございます!
— Shunya Ueta (@hurutoriya) 2020年9月11日
おっしゃるとおりだと思うので、変更しました☺️
フィードバックありがたいですhttps://t.co/S1MDDovpUj
最近、shiumachi さんのツイートでhttps://t.co/2ByvPpj2dQ
を知ったので、次はこれを使ってスキーマが同一かテストして連結するパターンも試してみます : )
当初は簡単にできるかなと思ったのですが、mergeならともかくconcatとなると、そんなに簡単にはいきません。
こうしたコードを使うケースというのは、大抵の場合探索的データ分析していたり、「素早く手軽に読み込みたい」というものなので、手軽さを失わないようにしながら最低限のチェックを行っていく必要があります。
連続したcsvを読み込むときにひっかかるケースの大きなものとしては、カラムの不一致とデータ型の不一致です。なので、この2つに絞ってバリデーションを行う、validate関数を作ってみました。
コードは長くなるので記事の末尾に載せています。
使い方は簡単で、まず、dfのリストの代わりに、 (pathlib.Path, df) のタプルのリストを作ります。
data = [(path, pd.read_csv(str(path))) for path in pathlib.Path(f_path).glob('*.csv')]
あとはこれを validate(data) に入れて、 pd.concat に渡すだけです。
pd.concat(validate(data))
もしカラムが一致していない場合は以下のようなエラーが出ます。
ValueError: ambiguous columns: file2.csv, file3.csv
もしカラムが一致していてもdtypeが一致していない場合は以下のようなエラーが出ます。
ValueError: inconsistent dtypes: int64, object in file2.csv, file4.csv
適当に作ったコードなのでエラー等あるかもしれません。
もし不具合等あったらお気軽にご報告ください。
コード全体(デモコードつき)
import pathlib import typing import pandas as pd # data definition ## valid data df1 = pd.DataFrame( [ {"c1": 100, "c2": "a100"}, {"c1": 101, "c2": "a101"}, ] ) ## valid data df2 = pd.DataFrame( [ {"c1": 200, "c2": "a200"}, {"c1": 202, "c2": "a202"}, ] ) ## invalid data: ambiguous column names df3 = pd.DataFrame( [ {"c1": 300, "c3": "a300"}, {"c1": 301, "c3": "a301"}, ] ) ## invalid data: inconsistent dtypes df4 = pd.DataFrame( [ {"c1": "400", "c2": "a400"}, {"c1": 401, "c2": "a401"}, ] ) ## dataset test case 1: ambiguous column names data1 = [ (pathlib.Path("file1.csv"), df1), (pathlib.Path("file2.csv"), df2), (pathlib.Path("file3.csv"), df3), ] ## dataset test case 2: inconsistent dtypes data2 = [ (pathlib.Path("file1.csv"), df1), (pathlib.Path("file2.csv"), df2), (pathlib.Path("file4.csv"), df4), ] def validate( data: typing.Sequence[typing.Tuple[pathlib.Path, typing.Sequence[pd.DataFrame]]] ) -> typing.Sequence[pd.DataFrame]: """simple data validation :param data: [(path, df)] :return: [df] """ for x, y in zip(data, data[1:]): if x[1].columns.tolist() != y[1].columns.tolist(): raise ValueError(f"ambiguous columns: {x[0]}, {y[0]}") for xd, yd in zip(x[1].dtypes, y[1].dtypes): if xd != yd: raise ValueError(f"inconsistent dtypes: {xd}, {yd} in {x[0]}, {y[0]}") return [x[1] for x in data] print("### validate ambiguous columns demo ###") try: pd.concat(validate(data1)) except ValueError as ve: print(ve) print("### validate inconsistent dtypes demo ###") try: pd.concat(validate(data2)) except ValueError as ve: print(ve)
謝辞
@aodag (zipのエレガントな書き方を教えてくれてありがとうございます)