IT業務効率化

pandasのread_csvにあるlow_memoryを理解したくて色々試してみた

pipenv docker

low_memoryとは?

言葉の通り、low_memoryをTrueにしておくとcsvを読み込む時にメモリ使用量を抑えることが可能になります。デフォルトでTrueに設定されています。

公式ドキュメント

参考資料

その挙動や使いどころを確認したく、いくつか記事を漁ってみました。特に参考になったのは下記の記事(質問掲示板です)

https://deepage.net/features/pandas-readcsv-deep.html#%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%BC%E3%81%AE%E4%BD%BF%E7%94%A8%E9%87%8F%E3%82%92%E6%8A%91%E3%81%88%E3%82%8B-low_memory

https://codeday.me/jp/qa/20181212/57958.html

この2つがlow_memoryについていくつか書かれていました。けれども

  • 型推論の時に使っている
  • 生成されたDataFrame自体のメモリ容量は変わらないけれども、読み込みじのメモリ使用量が変わる
  • 適用が推奨されていない?
  • 付けてても外してもそんなに変わりはない?

など情報がなんとなく読み取れるのですが、使いどころがよくわからなかったので試してみました。

結論

ほとんど差はありませんでした。

あくまで私の調査範囲ですが、特に気にしなくていいことにします。

ただ、まったく何も及ぼさない引数などないとは思うので、有識者の方々、宜しければコメントをください。

反省

レコード数、カラム数、型のバリエーションをもっともっと大きくすると変わるのかもしれませんが、私のmacbook airでできるスペック上では変化がみられませんでした。

実際に試してみたこと

実験用のcsv

id,name,random
1,Ingaborg,Mazda
2,Gilda,23
3,Lionel,Volkswagen
・・・

整数値の列、文字の列、文字と整数値の混合の列、1000行分です。

実験用のコード

import pandas as pd
from memory_profiler import profile

@profile
def memory_test1():
    df = pd.read_csv("MOCK_DATA.csv", low_memory=True)
    df.info(False, memory_usage=True)

memory_test1()

@profile
def memory_test2():
    df = pd.read_csv("MOCK_DATA.csv", low_memory=False)
    df.info(False, memory_usage=True)

memory_test2()

上記のコードを実行した結果はこうでした。

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Columns: 3 entries, id to random
dtypes: int64(1), object(2)
memory usage: 23.5+ KB
Filename: temp.py

Line #    Mem usage    Increment   Line Contents
================================================
     4     65.8 MiB     65.8 MiB   @profile
     5                             def memory_test1():
     6     67.2 MiB      1.4 MiB       df = pd.read_csv("MOCK_DATA.csv", low_memory=True)
     7     67.4 MiB      0.2 MiB       df.info(False, memory_usage=True)


<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Columns: 3 entries, id to random
dtypes: int64(1), object(2)
memory usage: 23.5+ KB
Filename: temp.py

Line #    Mem usage    Increment   Line Contents
================================================
    11     67.4 MiB     67.4 MiB   @profile
    12                             def memory_test2():
    13     67.6 MiB      0.2 MiB       df = pd.read_csv("MOCK_DATA.csv", low_memory=False)
    14     67.6 MiB      0.0 MiB       df.info(False, memory_usage=True)

これをみると一瞬「low_memory=Trueの方がメモリ使用料が少ない」と勘違いするのですが、違いました。

1つのファイルで2つ同時に実行してしまうと、どうしても下の関数の方が@profileのひり側に記載されるMem usageが大きくなってしまうことに気がつきました。low_memorがTrueでもFalseでもです。

ですので、別々のファイルとして実行することにしました。

yuki-PC-2:temp yuki$ python low_memory_true.py 
=====
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20980 entries, 0 to 20979
Columns: 3 entries, id to random
dtypes: int64(1), object(2)
memory usage: 491.8+ KB
=====
Filename: low_memory_true.py

Line #    Mem usage    Increment   Line Contents
================================================
     4     65.6 MiB     65.6 MiB   @profile
     5                             def memory_test():
     6     68.5 MiB      2.9 MiB       df = pd.read_csv("MOCK_DATA.csv", low_memory=True)
     7     68.5 MiB      0.0 MiB       print("=====")
     8     68.7 MiB      0.2 MiB       df.info(False, memory_usage=True)
     9     68.7 MiB      0.0 MiB       print("=====")
yuki-PC-2:temp yuki$ python low_memory_false.py 
=====
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20980 entries, 0 to 20979
Columns: 3 entries, id to random
dtypes: int64(1), object(2)
memory usage: 491.8+ KB
=====
Filename: low_memory_false.py

Line #    Mem usage    Increment   Line Contents
================================================
     4     65.6 MiB     65.6 MiB   @profile
     5                             def memory_test():
     6     68.5 MiB      2.8 MiB       df = pd.read_csv("MOCK_DATA.csv", low_memory=False)
     7     68.5 MiB      0.0 MiB       print("=====")
     8     68.7 MiB      0.2 MiB       df.info(False, memory_usage=True)
     9     68.7 MiB      0.0 MiB       print("=====")

これで比較するための環境が整いました。

pd.infoから出力されるDataFrame自体のメモリ使用量491.8+ KBはお互い差がありません。これは想定通りで、ドキュメントや他の記事にも記述されている通りでした。

しかし、私の仮説ではmemory_test関数の中のpd.read_csvのメモリ使用量に差が出るはずでした。しかし今のところ、差は出ていません。

  1. csvが小さすぎるのか
  2. もしくはデータ型を推定する時にメモリを使用すると記述されていたので、推定ロジックが実行されるようなデータになっていないか

どちらかになると思います。

ただ、とりあえずlow_memory, true or falseで変化を起こすことが重要なので、試せることを全部やってみて、差分が出るようになってから引き算する方式にしようと思います。

何も起きないのでがむしゃらにパラメータを変えまくってみる

実験用csv2

user_id
1
2
3
・・・
100000
(test)

testの文字はつけたり外したりして試してみます。testがあれば型はobject、testがなれければ型はintになると思います。

https://code-examples.net/ja/q/1720b53

こちらの記事を見ているとやはり型推論にメモリを要するような書き方をしていたいます。「基本はobjectだと思いながら読み込み、全て数値だった場合型を変更する」と読み取りました。このcsvでint型とobject型を行き来して実験します。

コードも一部変更

import pandas as pd
from memory_profiler import profile

@profile
def memory_test():
    df = pd.read_csv("MOCK_DATA2.csv", low_memory=False, dtype={'user_id': int})
    df.info(False, memory_usage=True)

memory_test()

型推論の時にメモリを利用しているらしいので、dtypeで誤った型を指定してみたりして荒ぶらせてみます。

結果

memory_profilerで表示される結果、下の出力結果の6行目にフォーカスします

Line #    Mem usage    Increment   Line Contents
================================================
     4     65.6 MiB     65.6 MiB   @profile
     5                             def memory_test():
     6     71.3 MiB      5.7 MiB       df = pd.read_csv("MOCK_DATA2.csv", low_memory=True,dtype={'user_id': int})
     7     71.6 MiB      0.2 MiB       df.info(False, memory_usage=True)
dtype = int指定dtype = object指定dtype指定なし
全て整数値
(100000行)
True:71.3 MiB
False:71.3 MiB
True:78.6 MiB
False:78.5 MiB
True:71.3 MiB
False:71.3 MiB
整数値と1行だけ文字列
(100000行)
※両方エラーTrue:78.6 MiB
False:78.5 MiB
True:78.6 MiB
False:78.5 MiB
全て文字列
(100000行)
※両方エラーTrue:72.4 MiB
False:72.3 MiB
True:72.2 MiB
False:72.1 MiB
文字列と1行だけ整数値
(100000行)
※両方エラーTrue:72.4MiB
False:72.1 MiB
True:72.1 MiB
False:72.2 MiB

あんまり変わらない。。。

面倒臭いので標準偏差等、計測していませんが、0.4MiBくらいは2、3回実行したらズレます。

 

ABOUT ME
hirayuki
今年で社会人3年目になります。 日々体当たりで仕事を覚えています。 テーマはIT・教育です。 少しでも技術に親しんでもらえるよう、noteで4コマ漫画も書いています。 https://note.mu/hirayuki