[Python3] Pandas v1.0-(九)高性能Pandas:eval()とquery()


文書ディレクトリ
  • 十二、高性能Pandas:eval()とquery()
  • (一)query()とeval()の設計動機:複合代数式
  • (二)用pandas.eval()高性能演算
  • を実現
  • pd.eval()がサポートする演算
  • (1)算術演算子
  • (2)比較演算子
  • (3)ビット演算子
  • (4)オブジェクト属性とインデックス
  • (5)その他の演算

  • (三)用DataFrame.eval()実装カラム間演算
  • 1. DataFrameでeval()新規列
  • 2. DataFrame.eval()ローカル変数
  • を使用
  • (四)DataFrame.query()メソッド
  • (五)性能決定使用タイミング


  • [ Pandas version: 1.0.1 ]
    十二、高性能Pandas:eval()とquery()
    Pandasデータ科学生態環境の強大な力はNumPyとPandasの基礎の上で創立して、そして直観的な文法を通じて基本操作をC言語に変換します:NumPyの中で量子化/放送演算で、Pandasの中でグループ型の演算です.
    これらの抽象機能は、多くの問題を簡潔かつ効率的に解決できますが、一時的な中間オブジェクトを作成する必要があることがよくあります.これにより、計算時間とメモリが大幅に消費されます.
    Pandasは0.13版から実験的なツールを導入し,ユーザがC言語速度の操作を直接実行できるようにし,中間配列の構成に苦労する必要がない.これらはeval()およびquery()の関数であり、いずれもNumexprパッケージに依存する.
    (一)query()とeval()の設計動機:複合代数式
    NumPyとPandasの高速量子化演算は、通常のPythonサイクルやリストの統合よりもずっと速い.
    #          
    import numpy as np
    rng = np.random.RandomState(42)
    x = rng.rand(int(1E6))
    y = rng.rand(int(1E6))
    %timeit x + y
    # 3.54 ms ± 172 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    %timeit np.fromiter((xi + yi for xi, yi in zip(x, y)), dtype=x.dtype, count=len(x))
    # 470 ms ± 29.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    しかし、この演算は複合代数式(compound expression)の問題を処理する際に効率が低く、各セグメントの中間プロセスにメモリを明示的に割り当てる必要がある.
    x配列とy配列が非常に大きい場合、演算には多くの時間とメモリ消費がかかります.
    mask = (x > 0.5) & (y < 0.5)
    
    #   NumPy          ,       :
    # tmp1 = (x > 0.5)
    # tmp2 = (y < 0.5)
    # mask = tmp1 & tmp2
    

    Numexprパッケージは、中間プロセスにすべてのメモリを割り当てない前提で、要素から要素への複合代数演算(NumPyスタイルの文字列代数式で演算)を完了することができます.
  • の利点:Numexprは代数式を計算する際に一時配列にすべてのメモリを割り当てる必要がないため、計算はNumPyよりも効率的であり、特に大型配列
  • の処理に適している.
  • Pandasのeval()およびquery()ツールもNumexprに基づく
  • である.
    import numexpr
    mask_numexpr = numexpr.evaluate('(x > 0.5) & (y < 0.5)')
    np.allclose(mask, mask_numexpr)
    # True
    

    (二)pandas.eval()高性能演算を実現
    Pandasのeval()関数は文字列代数式でDataFrameの高性能演算を実現した.
    import pandas as pd
    nrows, ncols = 100000, 100
    rng = np.random.RandomState(42)
    df1, df2, df3, df4 = (pd.DataFrame(rng.rand(nrows, ncols)) for i in range(4))
    
    #   Pandas      DataFrame  
    %timeit df1 + df2 + df3 + df4
    # 93.4 ms ± 8.72 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    # pd.eval                (               ,     )
    %timeit pd.eval('df1 + df2 + df3 + df4')
    # 49.4 ms ± 3.82 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    np.allclose(df1 + df2 + df3 + df4, pd.eval('df1 + df2 + df3 + df4'))
    # True
    
  • pandas.eval() - pandas 1.0.1 documentation

  • pd.eval()がサポートする演算
    #          DataFrame
    df1, df2, df3, df4, df5 = (pd.DataFrame(rng.randint(0, 100, (100, 3))) for i in range(5))
    

    (1)算術演算子pd.eval()はすべての演算子をサポートします
    result1 = -df1 * df2 / (df3 + df4) - df5
    result2 = pd.eval('-df1 * df2 / (df3 + df4) - df5')
    np.allclose(result1, result2)
    # True
    

    (2)比較演算子pd.eval()は、チェーン代数式(chained expression)を含むすべての比較演算子+, -, *, /, **, %, //をサポートする.
    result1 = (df1 < df2) & (df2 <= df3) & (df3 != df4)
    result2 = pd.eval('df1 < df2 <= df3 != df4')
    np.allclose(result1, result2)
    # True
    

    (3)ビット演算子pd.eval()| (or), & (and), ~ (not)等位演算子をサポートし、またブール型の代数式でandor等の文字面値を使用することもできる.
    result1 = (df1 < 0.5) & (df2 < 0.5) | (df3 < df4)
    result2 = pd.eval('(df1 < 0.5) & (df2 < 0.5) | (df3 < df4)')
    np.allclose(result1, result2)
    # True
    
    result3 = pd.eval('(df1 < 0.5) and (df2 < 0.5) or (df3 < df4)')
    np.allclose(result1, result3)
    # True
    

    (4)オブジェクト属性とインデックスpd.eval()は、obj.attr構文によってオブジェクト属性を取得し、obj[index]構文によってオブジェクトインデックスを取得することができる
    result1 = df2.T[0] + df3.iloc[1]
    result2 = pd.eval('df2.T[0] + df3.iloc[1]')
    np.allclose(result1, result2)
    # True
    

    (5)その他の演算pd.eval()は、関数呼び出し、条件文、ループ、およびより複雑な演算を一時的にサポートしません(ただし、Numexprによって実装できます).
    (三)DataFrameを用いる.eval()実装カラム間演算pd.eval()はPandasの最上位関数であるため、DataFrameにはeval()の方法で同様の演算が可能である.eval()メソッドを使用する利点は、カラム名を使用して演算できることです.
    df = pd.DataFrame(rng.rand(1000, 3), columns=['A', 'B', 'C'])
    df.head()
    #           A         B         C
    # 0  0.374540  0.950714  0.731994
    # 1  0.598658  0.156019  0.155995
    # 2  0.058084  0.866176  0.601115
    # 3  0.708073  0.020584  0.969910
    # 4  0.832443  0.212339  0.181825
    
    #  od.eval()              
    result1 = (df['A'] + df['B']) / (df['C'] - 1)
    result2 = pd.eval('(df.A + df.B) / (df.C - 1)')
    np.allclose(result1, result2)
    # True
    
    #  DataFrame.eval()                 
    result3 = df.eval('(A + B) / (C - 1)')
    np.allclose(result1, result3)
    # True
    
  • 変数としてカラム名を用いる代数式を計算することも同様に可能である
  • .
  • pandas.DataFrame.eval - pandas 1.0.1 documentation

  • 1.DataFrame.eval()新規列DataFrame.eval()はまた、新しいカラムを作成することもできます.
    df.head()
    #           A         B         C
    # 0  0.374540  0.950714  0.731994
    # 1  0.598658  0.156019  0.155995
    # 2  0.058084  0.866176  0.601115
    # 3  0.708073  0.020584  0.969910
    # 4  0.832443  0.212339  0.181825
    
    #    df.eval()      'D'           
    df.eval('D = (A + B) / C', inplace=True)
    df.head()
    #           A         B         C         D
    # 0  0.374540  0.950714  0.731994  1.810472
    # 1  0.598658  0.156019  0.155995  4.837844
    # 2  0.058084  0.866176  0.601115  1.537576
    # 3  0.708073  0.020584  0.969910  0.751263
    # 4  0.832443  0.212339  0.181825  5.746085
    
    #         
    df.eval('D = (A - B) / C', inplace=True)
    df.head()
    #           A         B         C         D
    # 0  0.374540  0.950714  0.731994 -0.787130
    # 1  0.598658  0.156019  0.155995  2.837535
    # 2  0.058084  0.866176  0.601115 -1.344323
    # 3  0.708073  0.020584  0.969910  0.708816
    # 4  0.832443  0.212339  0.181825  3.410442
    

    2. DataFrame.eval()ローカル変数の使用DataFrame.eval()メソッドはまた、@シンボルを介してPythonを使用するローカル変数をサポートする.
  • @符号は「1つのカラム名ではなく変数名」を表し、2つの「ネーミングスペース」のリソース(カラム名のネーミングスペースとPythonオブジェクトのネーミングスペース)を活用して代数式
  • を計算する.
  • 注意:@記号はDataFrame.eval()メソッドでのみ使用でき、pd.eval()関数では使用できません.pd.eval()関数はPythonネーミングスペースの1つのコンテンツ
  • しか取得できないためです.
    column_mean = df.mean(1)
    result1 = df['A'] + column_mean
    result2 = df.eval('A + @column_mean')
    np.allclose(result1, result2)
    # True
    

    (四)DataFrame.query()メソッド
    DataFrameは文字列代数式の演算に基づいてquery()と呼ばれる別の方法を実現した.
  • query()の方法はまた、@符号で局所変数
  • を参照することをサポートする.
    result1 = df[(df.A < 0.5) & (df.B < 0.5)]
    result2 = pd.eval('df[(df.A < 0.5) & (df.B < 0.5)]')
    np.allclose(result1, result2)
    # True
    
    #    DataFrame       ,    `DataFrame.eval()`  
    #            query()  
    
    result2 = df.query('A < 0.5 and B < 0.5')
    np.allclose(result1, result2)
    # True
    
    Cmean = df['C'].mean()
    result1 = df[(df.A < Cmean) & (df.B < Cmean)]
    result2 = df.query('A < @Cmean and B < @Cmean')
    np.allclose(result1, result2)
    # True
    

    (五)性能決定使用タイミング
    この2つの関数を使用するかどうかを考えるには、計算時間とメモリ消費、メモリ消費がより重要な影響要因であるという2つの側面を考える必要があります.
  • NumPy配列またはPandasに関するDataFrameの各複合代数式は、一時配列
  • を生成する.
    x = df[(df.A < 0.5) & (df.B < 0.5)]
    
    #       :
    # tmp1 = df.A < 0.5
    # tmp2 = df.B < 0.5
    # tmp3 = tmp1 & tmp2
    # x = df[tmp3]
    
  • 一時データFrameのメモリ要件がシステムメモリよりも大きい場合は、eval()query()の代数式
  • を使用することが望ましい.
    #                    :
    df.values.nbytes
    # 32000
    

    性能面では、最大のシステムメモリが使用されていなくても、eval()の計算速度は通常の方法よりも速い.
  • 現在のパフォーマンスのボトルネックは、一時的なDataFrameとシステムCPUのL 1およびL 2キャッシュとの比較となり、システムキャッシュが十分に大きい場合、eval()は、異なるキャッシュ間で一時ファイル
  • をゆっくり移動することを回避することができる.
  • 実際の作業では、一般的な計算方法とeval/queryの計算方法の計算時間の違いは、常にそれほど顕著ではなく、一般的な方法は、より小さな配列を処理する際に、かえって
  • よりも速い.eval/queryメソッドの利点は、主にメモリを節約することであり、構文もより簡潔になることがあります.
    Pandas関連読書:
    [Python3] Pandas v1.0-(一)オブジェクト、データ値と演算[python 3]Pandas v 1.0-(二)欠落値の処理[Python 3]Pandas v 1.0-(3)階層インデックス[python 3]Pandas v 1.0-(四)データセットのマージ[python 3]Pandas v 1.0-(五)累計とパケット[Python 3]Pandas v 1.0-(六)データピボットテーブル[python 3]Pandas v 1.0-(7)量子化文字列操作[python 3]Pandas v 1.0-(8)処理時系列[python 3]Pandas v 1.0——(九)高性能Pandas:eval()とquery()【本文】
    『Pythonデータ科学マニュアル』よりまとめ