🦆

pandasで読めない列数の異なるcsvをduckdbで読む

2025/02/04に公開

列数の異なるcsvファイルとは

6, 54
58,81,62
75,84,64,21,55
20,71,55,32

こういうやつです。
例えば機器から吐き出されるcsvファイルで一行目より下位の行の方が列数が多くなって出てくる事例があります。

このファイルをpandasのデータフレームにしてみる。

ということで以下のコードで脳死でpandasのデータフレームに変換します。

import pandas as pd
df = pd.read_csv("./csv_file1.csv")
print(df)

すると以下のようなエラーがでました。

pandas.errors.ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 5

3行目で予想していた列数の3より多い5が出たのでエラーです...という意味みたいです。
どうもpandasは最初の数行(今回は2行だがデフォルトが2行かどうかは不明)を見て、データフレームの列数を決定するようです。なのでその列数より大きい列数を持つ場合エラーになってしまいデータが読み取れません。

解決策1:最大列数を調べ、足りない部分は空の値でデータフレームを埋める。

だったらpandas以外でcsvファイルを開き、最大列数を調べてから最大列数以外の列数をもつ他の行の値を空値で埋めれば良いかと思って作ったコードが以下です。

import pandas as pd

with open('./csv_file1.csv', 'r', encoding='utf-8') as file:
    lines = file.readlines()

comma_count_max = max(line.count(',') for line in lines)
columns = [f'col_{i+1}' for i in range(comma_count_max + 1)]

data = []
for line in lines:
    row_data = line.strip().split(',')
    # 必要に応じて空文字列を追加
    if len(row_data) < len(columns):
        row_data += [''] * (len(columns) - len(row_data))
    data.append(row_data)

df = pd.DataFrame(data, columns=columns)
print(df)

pandasで読むとエラーになるのでpythonのopen()でファイルを開いてから全ての行の中で一番多いカンマの数をもつ行を調べる。そしてカンマの数 + 1が最大の列数ということなのであとは最大列数より小さい列数の行を空文字で埋める...と言う感じ。
結果は以下

  col_1 col_2 col_3 col_4 col_5
0     6    54                  
1    58    81    62            
2    75    84    64    21    55
3    20    71    55    32      

これで要件は達成なんですが、例えば...

6, 54
58,81,62,,,,,
75,84,64,21,55
20,71,55,32

こう言ったデータの場合は

  col_1 col_2 col_3 col_4 col_5 col_6 col_7 col_8
0     6    54                                    
1    58    81    62                              
2    75    84    64    21    55                  
3    20    71    55    32        

となります。
数行なら良いですが、csvファイルを開いて、各行に最大カンマ分のデータを足して...という工程を重いファイルでやると大変そうだなと思いました。

解決策2:duckdbのnull_paddingオプションを使用する。

duckdbのread_csvにはnull_paddingという空白部分をnull値で埋めるというオプションがある。

import duckdb
con = duckdb.connect(database=":memory:")
print(con.sql("SELECT * FROM read_csv('csv_file1.csv',null_padding=true)"))

この三行のコードを走らせると...?

┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ column0 │ column1 │ column2 │ column3 │ column4 │ column5 │ column6 │ column7 │
│  int64  │  int64  │  int64  │  int64  │  int64  │ varchar │ varchar │ varchar │
├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│       6 │      54 │    NULL │    NULL │    NULL │ NULL    │ NULL    │ NULL    │
│      58 │      81 │      62 │    NULL │    NULL │ NULL    │ NULL    │ NULL    │
│      75 │      84 │      64 │      21 │      55 │ NULL    │ NULL    │ NULL    │
│      20 │      71 │      55 │      32 │    NULL │ NULL    │ NULL    │ NULL    │
└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘

勝手に最大列数までnullで補完してからしかも最大列数分のヘッダーまでつけてくれます。
(ただ流石に最大のカンマの数だけ列数は作成されます。)

ちなみにデータフレームに変換したい時には

import duckdb
con = duckdb.connect(database=":memory:")
print(con.sql("SELECT * FROM read_csv('csv_file1.csv',null_padding=true)").df())

このように末尾に.df()をつけるとpandasのデータフレームに変換してくれます。

もしお困りの方試してみてください🙌

Discussion