web-dev-qa-db-ja.com

パンダを使用した「大規模データ」ワークフロー

私はパンダを学びながら何ヶ月もの間この質問に対する答えをパズルで解こうとしました。私は私の日々の仕事にSASを使っていますが、それがアウトオブコアサポートには最適です。しかし、SASは、他の多くの理由からソフトウェアの一部としては恐ろしいものです。

いつか私はSASの使用をpythonとpandasに置き換えたいと思っていますが、現在大規模なデータセットのためのアウトオブコアワークフローはありません。私は分散ネットワークを必要とする「ビッグデータ」について話しているのではなく、むしろメモリには収まらないがハードドライブには収まるには小さすぎるファイルを扱っています。

私の最初の考えは、HDFStoreを使って大きなデータセットをディスクに保存し、必要な部分だけを分析のためにデータフレームに入れることです。他の人は、MongoDBをより使いやすい代替手段として挙げています。私の質問はこれです:

以下を達成するためのベストプラクティスワークフローは何ですか。

  1. フラットファイルを永続的なディスク上のデータベース構造にロードする
  2. そのデータベースに問い合わせてデータを取得し、パンダデータ構造に送り込む
  3. パンダの部分を操作した後にデータベースを更新する

特に「ラージデータ」にパンダを使用している人からは、現実世界の例が非常に高く評価されます。

編集 - 私がこれをどのように機能させたいかの例:

  1. 大きなフラットファイルを繰り返しインポートして、永続的なディスク上のデータベース構造に格納します。これらのファイルは通常、大きすぎてメモリに収まりません。
  2. Pandasを使うために、私はメモリに収まることができるこのデータのサブセット(通常は一度に数カラムだけ)を読みたいと思います。
  3. 選択した列に対してさまざまな操作を実行して、新しい列を作成します。
  4. それからこれらの新しい列をデータベース構造に追加する必要があります。

これらの手順を実行するためのベストプラクティス方法を見つけようとしています。パンダやpytablesに関するリンクを読むと、新しいコラムを追加することが問題になる可能性があるようです。

編集 - 特にジェフの質問に答える:

  1. 消費者信用リスクモデルを構築しています。データの種類には、電話、SSN、および住所の特性が含まれます。プロパティ値私が毎日使用するデータセットには、混合データタイプの平均で1,000から2,000のフィールドがあります。数値データと文字データの両方の連続変数、名義変数、序数変数です。行を追加することはめったにありませんが、新しい列を作成する操作は多く実行します。
  2. 一般的な操作では、条件付きロジックを使用して複数の列を結合して新しい複合列にします。たとえば、if var1 > 2 then newvar = 'A' Elif var2 = 4 then newvar = 'B'です。これらの操作の結果、データセット内のすべてのレコードに対して新しい列ができます。
  3. 最後に、これらの新しい列をディスク上のデータ構造に追加します。ステップ2を繰り返して、クロス集計と記述統計を使用してデータを調べ、モデルに対する興味深い直感的な関係を見つけようとしました。
  4. 典型的なプロジェクトファイルは通常約1GBです。ファイルは、行が消費者データのレコードで構成されるように編成されています。各行は、各レコードに対して同数の列を持ちます。これは常に当てはまります。
  5. 新しい列を作成するときに行でサブセット化することはめったにありません。しかし、レポートを作成したり記述統計を生成したりするときには、行をサブセット化するのが一般的です。たとえば、私は特定の業種のために単純な頻度を作成したいと思うかもしれません、小売クレジットカードを言います。これを行うには、私が報告したいコラムに加えて、事業分野=小売のレコードだけを選択します。ただし、新しい列を作成するときは、すべての行のデータと、操作に必要な列だけを取得します。
  6. モデリングプロセスでは、すべての列を分析し、いくつかの結果変数との興味深い関係を探し、それらの関係を記述する新しい複合列を作成する必要があります。私が探求するコラムは通常小さなセットで行われます。たとえば、プロパティ値を扱う20列のセットに注目し、それらがローンのデフォルトにどのように関連するかを観察します。それらを調べて新しいコラムを作成したら、次に別のコラムグループ、たとえば大学教育に進み、プロセスを繰り返します。私がしていることは、私のデータといくつかの結果との関係を説明する候補変数を作成することです。このプロセスの最後に、これらの複合列から方程式を作成するいくつかの学習手法を適用します。

データセットに行を追加することはめったにありません。私はほとんど常に新しいコラム(統計学/機械学習の専門用語における変数や機能)を作成します。

850
Zelazny7

私は日常的にこのように数十ギガバイトのデータを使っています。私は、クエリを介して読み取り、データを作成し、そして追加するテーブルをディスク上に持っています。

データの保存方法に関するいくつかの提案については、 docs および このスレッドの後半 )を読む価値があります。

次のように、データの保存方法に影響する詳細情報
あなたができる限り詳細に説明します。そして私はあなたが構造を開発するのを手助けすることができます。

  1. データのサイズ、行数、列数、列の種類。行を追加するのですか、それとも列だけを追加するのですか。
  2. 典型的な操作はどのようになりますか。例えば。列に対してクエリを実行して一連の行と特定の列を選択してから、(インメモリ)操作を実行し、新しい列を作成して保存します。
    (玩具の例を挙げることで、より具体的な推奨事項を提示できる可能性があります。)
  3. その処理の後、あなたは何をしますか?ステップ2はアドホックですか、それとも繰り返し可能ですか?
  4. フラットファイルの入力:Gb単位のおおよその合計サイズ。これらはどのように編成されていますか。レコードで?それぞれに異なるフィールドが含まれていますか、それとも各ファイルのすべてのフィールドを含むファイルごとにいくつかのレコードがありますか?
  5. 基準に基づいて行のサブセット(レコード)を選択したことがありますか(例:フィールドA> 5の行を選択します)。それから何かをしますか、それともすべてのレコードを含むフィールドA、B、Cを選択しますか(そしてそれから何かをします)?
  6. すべての列を(グループで)「作業」していますか。それとも、レポートにしか使用できない割合が多いですか(たとえば、データを残しておきたいが、その列を明示する必要はありません)。最終結果の時間)?

溶液

あなたが 少なくとも0.10.1 がインストールされているパンダがあることを確認してください。

ファイルの繰り返しチャンクごとのチャンク および 複数テーブルクエリ )の読み取り.

Pytablesは行単位で操作するように最適化されているので(これはあなたが問い合わせるものです)、我々はフィールドの各グループに対してテーブルを作成します。このようにすると、小さなフィールドのグループを選択するのが簡単になります(大きなテーブルでも動作しますが、この方法で実行するほうが効率的です。将来この制限を修正できる可能性があります。とにかくより直感的):
(以下は疑似コードです)

import numpy as np
import pandas as pd

# create a store
store = pd.HDFStore('mystore.h5')

# this is the key to your storage:
#    this maps your fields to a specific group, and defines 
#    what you want to have as data_columns.
#    you might want to create a Nice class wrapping this
#    (as you will want to have this map and its inversion)  
group_map = dict(
    A = dict(fields = ['field_1','field_2',.....], dc = ['field_1',....,'field_5']),
    B = dict(fields = ['field_10',......        ], dc = ['field_10']),
    .....
    REPORTING_ONLY = dict(fields = ['field_1000','field_1001',...], dc = []),

)

group_map_inverted = dict()
for g, v in group_map.items():
    group_map_inverted.update(dict([ (f,g) for f in v['fields'] ]))

ファイルを読み込み、ストレージを作成する(基本的にappend_to_multipleが行うことをする)

for f in files:
   # read in the file, additional options hmay be necessary here
   # the chunksize is not strictly necessary, you may be able to Slurp each 
   # file into memory in which case just eliminate this part of the loop 
   # (you can also change chunksize if necessary)
   for chunk in pd.read_table(f, chunksize=50000):
       # we are going to append to each table by group
       # we are not going to create indexes at this time
       # but we *ARE* going to create (some) data_columns

       # figure out the field groupings
       for g, v in group_map.items():
             # create the frame for this group
             frame = chunk.reindex(columns = v['fields'], copy = False)    

             # append it
             store.append(g, frame, index=False, data_columns = v['dc'])

これで、すべてのテーブルをファイルに格納できました(実際には、必要に応じて別々のファイルに格納できますが、おそらくgroup_mapにファイル名を追加する必要がありますが、おそらくこれは不要です)。

これは、カラムを取得して新しいカラムを作成する方法です。

frame = store.select(group_that_I_want)
# you can optionally specify:
# columns = a list of the columns IN THAT GROUP (if you wanted to
#     select only say 3 out of the 20 columns in this sub-table)
# and a where clause if you want a subset of the rows

# do calculations on this frame
new_frame = cool_function_on_frame(frame)

# to 'add columns', create a new group (you probably want to
# limit the columns in this new_group to be only NEW ones
# (e.g. so you don't overlap from the other tables)
# add this info to the group_map
store.append(new_group, new_frame.reindex(columns = new_columns_created, copy = False), data_columns = new_columns_created)

Post_processingの準備ができたら

# This may be a bit tricky; and depends what you are actually doing.
# I may need to modify this function to be a bit more general:
report_data = store.select_as_multiple([groups_1,groups_2,.....], where =['field_1>0', 'field_1000=foo'], selector = group_1)

Data_columnsについては、実際には _ any _ data_columnsを定義する必要はありません。列に基づいて行を選択し直すことができます。例えば。何かのようなもの:

store.select(group, where = ['field_1000=foo', 'field_1001>0'])

これらは、最終的なレポート生成段階で最も興味深いことがあります(基本的にデータ列は他の列から分離されているため、多くの定義をした場合、効率に多少影響する可能性があります)。

あなたはまたしたいかもしれません:

  • フィールドのリストを受け取り、groups_mapでグループを検索し、結果を連結するように結果を連結する関数を作成します(これは基本的にselect_as_multipleが行うことです)。 このようにして、構造はあなたにとってかなり透明になります。
  • 特定のデータ列に索引を付けます(行サブセット化がはるかに高速になります)。
  • 圧縮を有効にします。

質問がある場合はお知らせください。

542
Jeff

私は上記の答えが私が非常に有用であると思った簡単なアプローチを欠いていると思う。

大きすぎてメモリにロードできないファイルがある場合は、そのファイルを複数の小さいファイルに分割します(行または列によって)。

例:30日分の30 GBまでのサイズの取引データの場合、1日に1 GBまでのサイズのファイルに分割します。その後、それぞれのファイルを別々に処理し、最後に結果を集計します。

最大の利点の1つは、ファイルの並列処理(複数のスレッドまたはプロセス)を可能にすることです。

もう1つの利点は、ファイル操作(例では日付の追加/削除など)が通常のShellコマンドで実行できることです。これは、より高度で複雑なファイル形式では不可能です。

このアプローチはすべてのシナリオを網羅しているわけではありませんが、多くのシナリオで非常に役立ちます。

116
user1827356

質問の2年後に、現在、「アウトオブコア」パンダと同等のものがあります: dask 。素晴らしいですね!それはパンダの機能のすべてをサポートするわけではありませんが、あなたは本当にそれを得ることができます。

59
Private

データセットが1〜20GBの場合は、48GBのRAMを搭載したワークステーションを入手する必要があります。その後、Pandasはデータセット全体をRAMに保持できます。ここで探している答えではないことを私は知っていますが、4GBのRAMを搭載したノートブックで科学計算を行うのは合理的ではありません。

58
rjurney

これは古いスレッドですが、 Blaze ライブラリはチェックする価値があると思います。それはこのような状況のために作られています。

ドキュメントより:

Blazeは、NumPyとPandasの使いやすさを、分散コンピューティングとアウトオブコアコンピューティングに拡張します。 Blazeは、NumPy ND-ArrayやPandas DataFrameに似たインターフェースを提供しますが、これらのよく知られたインターフェースをPostgresやSparkのような他のさまざまな計算エンジンにマッピングします。

編集: ところで、NumPyの作者であるContinuumIOとTravis Oliphantによってサポートされています。

50
chishaku

これはpymongoの場合です。私はまた、PythonでSQL Server、SQLite、HDF、ORM(SQLAlchemy)を使用してプロトタイプを作成しました。何よりもまずpymongoはドキュメントベースのDBなので、それぞれの人はドキュメント(属性のdict)になるでしょう。多くの人々がコレクションを形成し、あなたは多くのコレクション(人々、株式市場、収入)を持つことができます。

pd.dateframe - > pymongo注:read_csvchunksizeを使用して5から10kレコードに保ちます(pymongoは大きい場合はソケットを削除します)。

aCollection.insert((a[1].to_dict() for a in df.iterrows()))

クエリ:gt =より大きい...

pd.DataFrame(list(mongoCollection.find({'anAttribute':{'$gt':2887000, '$lt':2889000}})))

.find()はイテレータを返すので、私は一般的にichunkedを使って小さなイテレータに切り刻みます。

通常、10個のデータソースを貼り付けるので、結合はどうでしょうか。

aJoinDF = pandas.DataFrame(list(mongoCollection.find({'anAttribute':{'$in':Att_Keys}})))

それから(私の場合は時々私はその「併合可能」の前に最初にaJoinDFに賛成しなければならない)

df = pandas.merge(df, aJoinDF, on=aKey, how='left')

そして、以下の更新方法であなたのメインコレクションに新しい情報を書くことができます。 (論理コレクションと物理データソース).

collection.update({primarykey:foo},{key:change})

小さいルックアップでは、ただ非正規化してください。たとえば、ドキュメントにコードがあり、フィールドコードのテキストを追加して、ドキュメントを作成するときにdictルックアップを実行するだけです。

これで、人をベースにしたNiceデータセットができました。それぞれのケースでロジックを解き放ち、より多くの属性を作成できます。最後にあなたはパンダにあなたの3からメモリの最大の重要な指標を読み込んでピボット/ agg/data探査をすることができます。これは私のために数/ビッグテキスト/カテゴリ/コード/フロート/を持つ300万レコードのために働きます...

MongoDBに組み込まれている2つの方法(MapReduceと集約フレームワーク)も使用できます。 集約フレームワークの詳細については、こちらを参照してください。 - MapReduceよりも簡単で、集約的な作業を短時間で行うのに便利なようです。自分のフィールドやリレーションを定義する必要がないことに注意してください。また、アイテムをドキュメントに追加することができます。急速に変化しているでこぼこ、パンダ、pythonツールセットの現在の状態で、MongoDBは私がただ仕事に就くのを助けます:)

45

私はこれを少し遅く見つけました、しかし私は同様の問題(住宅ローン前払いモデル)で働きます。私の解決策は、パンダのHDFStoreレイヤーを飛ばしてストレートのpytablesを使うことでした。各列を個々のHDF5配列として最終ファイルに保存します。

私の基本的なワークフローは、まずデータベースからCSVファイルを取得することです。私はそれをgzipしているので、それほど大きくはありません。それをpythonで繰り返し、各行を実際のデータ型に変換してHDF5ファイルに書き込むことで、それを行指向のHDF5ファイルに変換します。これには数十分かかりますが、行単位でしか動作しないため、メモリを使用しません。次に、行指向のHDF5ファイルを列指向のHDF5ファイルに「転置」します。

テーブル転置は次のようになります。

def transpose_table(h_in, table_path, h_out, group_name="data", group_path="/"):
    # Get a reference to the input data.
    tb = h_in.getNode(table_path)
    # Create the output group to hold the columns.
    grp = h_out.createGroup(group_path, group_name, filters=tables.Filters(complevel=1))
    for col_name in tb.colnames:
        logger.debug("Processing %s", col_name)
        # Get the data.
        col_data = tb.col(col_name)
        # Create the output array.
        arr = h_out.createCArray(grp,
                                 col_name,
                                 tables.Atom.from_dtype(col_data.dtype),
                                 col_data.shape)
        # Store the data.
        arr[:] = col_data
    h_out.flush()

それを読み返すと、次のようになります。

def read_hdf5(hdf5_path, group_path="/data", columns=None):
    """Read a transposed data set from a HDF5 file."""
    if isinstance(hdf5_path, tables.file.File):
        hf = hdf5_path
    else:
        hf = tables.openFile(hdf5_path)

    grp = hf.getNode(group_path)
    if columns is None:
        data = [(child.name, child[:]) for child in grp]
    else:
        data = [(child.name, child[:]) for child in grp if child.name in columns]

    # Convert any float32 columns to float64 for processing.
    for i in range(len(data)):
        name, vec = data[i]
        if vec.dtype == np.float32:
            data[i] = (name, vec.astype(np.float64))

    if not isinstance(hdf5_path, tables.file.File):
        hf.close()
    return pd.DataFrame.from_items(data)

さて、私は一般的にこれを大量のメモリを搭載したマシンで実行しているので、メモリ使用量には十分注意しないかもしれません。たとえば、デフォルトでは、ロード操作はデータセット全体を読み取ります。

これは一般的に私のために働くが、それは少し不格好であり、そして私は空想のピタブル魔法を使うことができない。

編集:デフォルトのレコード配列pytablesに対するこのアプローチの本当の利点は、テーブルを扱うことができないh5rを使ってRにデータをロードできることです。あるいは、少なくとも、私は異種のテーブルをロードすることができませんでした。

37

大きなデータ のユースケースに役立つトリックの1つは、浮動小数点精度を32ビットに減らすことによってデータの量を減らすことです。すべての場合に当てはまるわけではありませんが、多くのアプリケーションで64ビット精度はやり過ぎであり、2倍のメモリ節約はそれだけの価値があります。明白な点をさらに明確にするために:

>>> df = pd.DataFrame(np.random.randn(int(1e8), 5))
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000000 entries, 0 to 99999999
Data columns (total 5 columns):
...
dtypes: float64(5)
memory usage: 3.7 GB

>>> df.astype(np.float32).info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000000 entries, 0 to 99999999
Data columns (total 5 columns):
...
dtypes: float32(5)
memory usage: 1.9 GB
26
ytsaig

他の人が指摘したように、数年後に 'アウトオブコア'パンダと同等のものが出現しました: dask 。 daskはパンダやそのすべての機能の代わりになるものではありませんが、いくつかの理由で際立っています。

Daskは、並列配列、データフレーム、NumPy、Pandas、Pythonイテレータなどの一般的なインタフェースを大規模なものに拡張するリストなどの「ビッグデータ」コレクションの対話型計算ワークロードの動的タスクスケジューリングに最適化された分析コンピューティング用の柔軟な並列計算ライブラリです。メモリ環境よりも分散環境でも、ラップトップからクラスタまで拡張できます。

Daskは次の長所を強調しています。

  • よく知られていること:並列化されたNumPy配列とPandas DataFrameオブジェクトを提供する
  • 柔軟性:より多くのカスタムワークロードおよび他のプロジェクトとの統合のためのタスクスケジューリングインターフェースを提供します。
  • ネイティブ:PyDataスタックにアクセスして、ピュアPythonでの分散コンピューティングを可能にします。
  • 高速:高速数値アルゴリズムに必要な低オーバーヘッド、低レイテンシ、および最小限のシリアル化で動作します。
  • スケールアップ:数千コアのクラスタで弾力的に実行スケールダウン:単一プロセスでラップトップ上でセットアップして実行するのは簡単
  • レスポンシブ:インタラクティブコンピューティングを念頭に置いて設計されているため、人間を支援するための迅速なフィードバックと診断が提供されます。

そして簡単なコードサンプルを追加します。

import dask.dataframe as dd
df = dd.read_csv('2015-*-*.csv')
df.groupby(df.user_id).value.mean().compute()

このようにいくつかのパンダコードを置き換えます:

import pandas as pd
df = pd.read_csv('2015-01-01.csv')
df.groupby(df.user_id).value.mean()

そして、特に注目すべきことに、concurrent.futuresインターフェースを通じてカスタムタスクの提出に関する一般的な情報を提供します。

from dask.distributed import Client
client = Client('scheduler:port')

futures = []
for fn in filenames:
    future = client.submit(load, fn)
    futures.append(future)

summary = client.submit(summarize, futures)
summary.result()
15
wp78de

もう1つのバリエーション

パンダで行われる操作の多くはdbクエリとしても行うことができます(sql、mongo)

RDBMSまたはmongodbを使用すると、DBクエリで大量の集計を実行できます(大規模データ用に最適化されており、キャッシュとインデックスを効率的に使用できます)。

後で、パンダを使用して後処理を実行できます。

この方法の利点は、ロジックを高レベルの宣言型構文で定義しながら、大きなデータを処理するためのDBの最適化が得られることです。メモリ内で何をして何をするかを決定する詳細を扱う必要がありませんコアの。

クエリ言語とパンダは異なりますが、ロジックの一部を相互に変換するのは通常複雑ではありません。

12
Ophir Yoktan

ここで言及する価値があります Ray
これは分散計算フレームワークであり、パンダのための独自の実装が分散的に行われています。

パンダのインポートを置き換えるだけで、コードはそのまま機能するはずです。

# import pandas as pd
import ray.dataframe as pd

#use pd as usual

ここで詳細を読むことができます:

https://rise.cs.berkeley.edu/blog/pandas-on-ray/

9
lev

Ruffus を考えてみましょう。単純な方法で複数の小さなファイルに分割されたデータパイプラインを作成する場合。

8
Golf Monkey

私は最近同様の問題に遭遇しました。私は、データをまとまりとして読んで、同じcsvにまとめて書くときにそれを追加することを単純に見つけました。私の問題は、次のように特定の列の値を使用して、別のテーブルの情報に基づいて日付列を追加することでした。これは、daskとhdf5に混乱しているけれど、私のようにパンダに慣れている人を助けるかもしれません。

def addDateColumn():
"""Adds time to the daily rainfall data. Reads the csv as chunks of 100k 
   rows at a time and outputs them, appending as needed, to a single csv. 
   Uses the column of the raster names to get the date.
"""
    df = pd.read_csv(pathlist[1]+"CHIRPS_tanz.csv", iterator=True, 
                     chunksize=100000) #read csv file as 100k chunks

    '''Do some stuff'''

    count = 1 #for indexing item in time list 
    for chunk in df: #for each 100k rows
        newtime = [] #empty list to append repeating times for different rows
        toiterate = chunk[chunk.columns[2]] #ID of raster nums to base time
        while count <= toiterate.max():
            for i in toiterate: 
                if i ==count:
                    newtime.append(newyears[count])
            count+=1
        print "Finished", str(chunknum), "chunks"
        chunk["time"] = newtime #create new column in dataframe based on time
        outname = "CHIRPS_tanz_time2.csv"
        #append each output to same csv, using no header
        chunk.to_csv(pathlist[2]+outname, mode='a', header=None, index=None)
5
timpjohns

私はVaexパッケージを指摘したいのですが。

Vaexは、大きな表形式のデータセットを視覚化して探索するための、怠惰なOut-of-Core DataFrame(Pandasに似た)用のpythonライブラリです。平均、合計、数、標準偏差などの統計を、最大10億までのN次元グリッド上で計算できます(1091秒あたりのオブジェクト/行数。ヒストグラム、密度プロット、および3Dボリュームレンダリングを使用して視覚化が行われるため、ビッグデータをインタラクティブに探索できます。 Vaexは、最高のパフォーマンスを得るために、メモリマッピング、ゼロメモリコピーポリシー、および遅延計算を使用します(メモリを無駄にすることはありません)。

ドキュメントを見てください: https://vaex.readthedocs.io/en/latest/ APIはパンダのAPIに非常に近いです。

2
Rob