Python ビックデータを開く

大量のデータを処理する場合、データを一括で読み込むと以下のようなエラーメッセージが出ます。

killed

これは、メモリが上限を超えてしまったことを意味します。どうすれば実行できるようになるでしょうか。

メモリとは

コンピュータにおいて、プログラムやデータを記憶する装置のことです。

特に、RAMやROMなどの半導体記憶装置のことを指しますが、ここでは本題からずれますので割愛します。

したがって、メモリが不足するとプログラムのデータを記憶しておくことが困難になります。

データ作成

本記事では、分かりやすくするため、単純なデータ(csvファイル)を準備します。

list= []
for i in range(100):
    list.append(str(i) + ',csv,file')

with open('sample.csv', 'w') as f:
    f.write('\n'.join(list))

データを分割して読み込む

メモリの上限を超えてしまうような大量のデータは、基本的に分割して読み込みます。

yield

yieldを用いることで、ファイルの中身を一行ずつ返すことで、ファイル全体をメモリ上に読み込む必要がありません。

def file_generator(file):
    with open(file, encoding="utf-8") as f:
        for line in f:
            yield line

file_path = 'sample.csv'
gen = file_generator(file_path)

Pandasのchunksize

Pandasのchunksizeを用いることで、csvファイルから数行ずつ読み込むことができます。

import pandas as pd

reader = pd.read_csv('sample.csv', encoding='utf-8', chunksize=10, header=None)

for i in reader:
    print(i)

実行結果

   0    1     2
0  0  csv  file
1  1  csv  file
2  2  csv  file
3  3  csv  file
4  4  csv  file
5  5  csv  file
6  6  csv  file
7  7  csv  file
8  8  csv  file
9  9  csv  file
     0    1     2
10  10  csv  file
11  11  csv  file
・・・・・・・・・・・
88  88  csv  file
89  89  csv  file
     0    1     2
90  90  csv  file
91  91  csv  file
92  92  csv  file
93  93  csv  file
94  94  csv  file
95  95  csv  file
96  96  csv  file
97  97  csv  file
98  98  csv  file
99  99  csv  file

データから特定して読み込む

行の指定

Pandasのskiprows

csvファイルで特定の行を除外して読み込む場合、read_csvの引数にskiprowsを用います。

ファイルの先頭をその行数分スキップして読み込みます。

import pandas as pd
df=pd.read_csv('sample.csv',header=None)
print(df)
df=pd.read_csv('sample.csv',header=None,skiprows=10)
print(df)

実行結果

     0    1     2
0    0  csv  file
1    1  csv  file
2    2  csv  file
3    3  csv  file
4    4  csv  file
..  ..  ...   ...
95  95  csv  file
96  96  csv  file
97  97  csv  file
98  98  csv  file
99  99  csv  file

[100 rows x 3 columns]
     0    1     2
0   10  csv  file
1   11  csv  file
2   12  csv  file
3   13  csv  file
4   14  csv  file
..  ..  ...   ...
85  95  csv  file
86  96  csv  file
87  97  csv  file
88  98  csv  file
89  99  csv  file

[90 rows x 3 columns]

100×3から90×3に少なくなりました。

スキップする行番号をリストで指定することもできます。

import pandas as pd
df=pd.read_csv('sample.csv',header=None)
print(df)
df=pd.read_csv('sample.csv',header=None,skiprows=[0,2,4,95,97,99])
print(df)

実行結果

     0    1     2
0    0  csv  file
1    1  csv  file
2    2  csv  file
3    3  csv  file
4    4  csv  file
..  ..  ...   ...
95  95  csv  file
96  96  csv  file
97  97  csv  file
98  98  csv  file
99  99  csv  file

[100 rows x 3 columns]
     0    1     2
0    1  csv  file
1    3  csv  file
2    5  csv  file
3    6  csv  file
4    7  csv  file
..  ..  ...   ...
89  92  csv  file
90  93  csv  file
91  94  csv  file
92  96  csv  file
93  98  csv  file

[94 rows x 3 columns]

0,2,4,95,97,99行のデータをスキップして読み込んでいます。

Pandasのskipfooter

csvファイルの末尾をスキップする場合、read_csvの引数にskipfooterを用います。スキップする行数を整数で指定します。engine=”python”は、警告文を表示させないためです。

import pandas as pd
df=pd.read_csv('sample.csv',header=None)
print(df)
df=pd.read_csv('sample.csv',header=None,skipfooter=10,engine='python')
print(df)

実行結果

     0    1     2
0    0  csv  file
1    1  csv  file
2    2  csv  file
3    3  csv  file
4    4  csv  file
..  ..  ...   ...
95  95  csv  file
96  96  csv  file
97  97  csv  file
98  98  csv  file
99  99  csv  file

[100 rows x 3 columns]
     0    1     2
0    0  csv  file
1    1  csv  file
2    2  csv  file
3    3  csv  file
4    4  csv  file
..  ..  ...   ...
85  85  csv  file
86  86  csv  file
87  87  csv  file
88  88  csv  file
89  89  csv  file

[90 rows x 3 columns]

Pandasのnrows

csvファイルの最初の数行だけを読み込みたいときは、read_csvの引数にnrowsを用います。

import pandas as pd
df=pd.read_csv('sample.csv',header=None)
print(df)
df=pd.read_csv('sample.csv',header=None,nrows=10,engine='python')
print(df)

実行結果

     0    1     2
0    0  csv  file
1    1  csv  file
2    2  csv  file
3    3  csv  file
4    4  csv  file
..  ..  ...   ...
95  95  csv  file
96  96  csv  file
97  97  csv  file
98  98  csv  file
99  99  csv  file

[100 rows x 3 columns]
   0    1     2
0  0  csv  file
1  1  csv  file
2  2  csv  file
3  3  csv  file
4  4  csv  file
5  5  csv  file
6  6  csv  file
7  7  csv  file
8  8  csv  file
9  9  csv  file

列の指定

Pandasのusecols

特定の列だけを読み込む場合、read_csvの引数にusecolsを用います。

読み込む列番号をリストで指定します。

import pandas as pd
df=pd.read_csv('sample.csv',header=None)
print(df)
df=pd.read_csv('sample.csv',header=None, usecols=[0,2])
print(df)

実行結果

     0    1     2
0    0  csv  file
1    1  csv  file
2    2  csv  file
3    3  csv  file
4    4  csv  file
..  ..  ...   ...
95  95  csv  file
96  96  csv  file
97  97  csv  file
98  98  csv  file
99  99  csv  file

[100 rows x 3 columns]
     0     2
0    0  file
1    1  file
2    2  file
3    3  file
4    4  file
..  ..   ...
95  95  file
96  96  file
97  97  file
98  98  file
99  99  file

[100 rows x 2 columns]

変数、配列のサイズを定量化

上記を施すことでどのぐらいサイズ(メモリを使う量)が変化するのかをみたいとき、以下のような関数を指定します。

def print_varsize():
    import types
    print("{}{: >15}{}{: >10}{}".format('|','Variable Name','|','  Size','|'))
    print(" -------------------------- ")
    for k, v in globals().items():
        if hasattr(v, 'size') and not k.startswith('_') and not isinstance(v,types.ModuleType):
            print("{}{: >15}{}{: >10}{}".format('|',k,'|',str(v.size),'|'))
        elif hasattr(v, '__len__') and not k.startswith('_') and not isinstance(v,types.ModuleType):
            print("{}{: >15}{}{: >10}{}".format('|',k,'|',str(len(v)),'|'))

こちらの関数でデータサイズを測ってみましょう。

import pandas as pd

df01=pd.read_csv('sample.csv',header=None)

df02=pd.read_csv('sample.csv',header=None, usecols=[0,2])

df03=pd.read_csv('sample.csv',header=None,skiprows=10)


def file_generator(file):
    with open(file, encoding="utf-8") as f:
        for line in f:
            yield line

file_path = 'sample.csv'
gen = file_generator(file_path)


def print_varsize():
    import types
    print("{}{: >15}{}{: >10}{}".format('|','Variable Name','|','  Size','|'))
    print(" -------------------------- ")
    for k, v in globals().items():
        if hasattr(v, 'size') and not k.startswith('_') and not isinstance(v,types.ModuleType):
            print("{}{: >15}{}{: >10}{}".format('|',k,'|',str(v.size),'|'))
        elif hasattr(v, '__len__') and not k.startswith('_') and not isinstance(v,types.ModuleType):
            print("{}{: >15}{}{: >10}{}".format('|',k,'|',str(len(v)),'|'))

print_varsize()


実行結果

|  Variable Name|      Size|
 --------------------------
|           df01|       300|
|           df02|       200|
|           df03|       270|
|      file_path|        10|
|           line|        11|

まとめ

大量のデータを開くためには、分割して開く、または特定して開く2つの方法があります。

本記事では、データを分割する方法として2つ、特定して開く方法として3つ紹介しました。

Pythonはたくさんのモジュール、機能があり、この他にもメモリ上限を超えないようにファイルを開く方法があるかと思います。

また、daskというライブラリは、大量のデータを扱うことに特化したライブラリとしてdaskというものもあるようです。勉強中です。