データ分析の長期インターン備忘録 ~僕とfor文とIterator(イテレータ)~

初めての長期インターン


 2月から始めた長期インターンが気づけば半年以上過ぎていました。
 業務内容はデータ分析に関することで、Pythonを触ったことがあるものの、「Pandas?何それおいしいの?」状態から始めて色々試行錯誤して続けてきました。初めてのこともあって学べることが多かったのでここでPandasの色々についてまとめようと思い記事にしました。

注)今回の記事は主にPandas(Dataframe)についての記事です。
長期インターンのあれこれについては書かないので、長期インターンの感想を聞きたい、参考にしたいという方はご容赦ください。

博識な皆さんへ


 私はとてつもなくググるのが下手です。まして知識がないので自分の知りたい情報に行き着けず、いつも遠回りしています。さらに、PCも学校からの借り物でよわよわです。そのため、現時点では下に示すようなコードが一番早く、メモリ消費を抑えているつもりです。
 もっといいコードがあったらTwitter等でも構わないので是非教えてほしいです。


 本題に戻ります。

え...もしかして私のコードfor文多すぎ...?


 今でも課題の一つですが、データ分析のデータ量というのは基本的に膨大なものです。自分の業務はその膨大なデータから条件に合うデータを抽出するもので、csvにして約1GBものデータなのでネストのないfor文でも数千万loopもしてしまうため基本的にfor文等はあまり褒められたものじゃないんだとか...
 まあ、当然ですよね
 でも、使わないとコード書けないよーってことで、少しでもfor文の処理を速める、メモリの使用量を減らすために着目したが"iterator(イテレータ)"です。
 C++の授業でやったやんけぇ...


注)jupyter labで実行しました%%timeitはjupyterだから可能なのかも...


x = 10000
size = x*10

df = pd.DataFrame(pd.np.arange(size).reshape(x, 10))
df.columns = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

%%timeit
list = process_for(df)  


 仮のDataframe(df)、10000×10のものを作成してcolumnsを割り当てました。このprocess_forという自作関数の実行速度について様々なfor文を用いて実行してみました。まずは以下のコード


def process_for(df):
     arr = []
      for i in range(0, len(df)):
           if df.iloc[i, 0] >= 50:
                arr.append([df.iloc[i, 0], df.iloc[i, 1]])

 Dataframeを扱い始めて間もないころ、df.atdf.loc, df.ilocでDataframeの各要素の値を持ってこれることを知ったのでそれを用いてfor文を書いていました。

 その結果...

  • 624 ms ± 21.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)


 当時は( ´_ゝ`)フーンって感じ、では次のように書き換えて

def process_for(df):
     arr = []
      for row in df.itertuples(name=None):
           if row[1] >= 50:
                arr.append([row[1],row[2]])

 Iteratorについて調べてみたところitertuples()iterrows()があり、

itertuples()はiterrows()よりも速い

 という記述をとあるサイトで見つけたのでitertuples()を採用したところ...

  • 30.6 ms ± 1.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)


 全然ちゃうやんけ...なんでもっとはやく知らなかったんだ...
 そこでさらに調べてみて以下のコードに書き換えてみました

def process_for(df):
     arr = []
      for a, b in zip(df['a'], df['b']):
            if a >= 50:
                   arr.append([a, b])

 必要な列だけを用いたzip()を使ったfor文に変えました、すると

  • 9.17 ms ± 1.46 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)


 zip最強!また、zip(df.index)で列番号を取得できます。zipしか勝たん!!!


 10000×10のDataframeの前に、よりサイズの小さい10×10や100×10でも試してみましたが、速度の順位が覆ることなく、サイズが大きくなればなるほど差が開いていくばかりでした。
 また、Iterator(イテレータ)を用いたときの変数(process_forのrowやa, bのことを指すこととします)の型はDataframeの要素に応じたもので、今回用いたDataframe(df)では数値が要素に入っているのでint型でした。
 (てっきりpd系の型がついているのかと思った...)

 for文なんて別にどれも一緒だろうと思っていた自分にとっては驚きでした。Iterator(イテレータ)は知っていたものの速度等に差はないだろwwと思っていた自分を殴ってやりたいですね。
 これが一番早いなど思っていてもまずは疑ってかかることの大事さに気づかせてくれたので非常によかったです。

アディショナルタイム


 先のprocess_forのコードはDataframeから条件にあった列の要素を抽出してListにしていますが、本当はListではなくもう一度Dataframeにしたいのです。しかし、どうやったら一度でDataframeにできるかわからず、遠回りになってしまいますが、一度Listにしてからpd.Dataframe()で型を変換してDataframeにしています。
 もし、抽出したコードを一度でDataframeにする方法をご存知のかたは教えてほしいです。よろしくお願いいたします。


 つまらぬ内容、拙い文章ですが、最後までお読みいただきありがとうございました。