私はパンダを学びながら何ヶ月もの間この質問に対する答えをパズルで解こうとしました。私は私の日々の仕事にSASを使っていますが、それがアウトオブコアサポートには最適です。しかし、SASは、他の多くの理由からソフトウェアの一部としては恐ろしいものです。
いつか私はSASの使用をpythonとpandasに置き換えたいと思っていますが、現在大規模なデータセットのためのアウトオブコアワークフローはありません。私は分散ネットワークを必要とする「ビッグデータ」について話しているのではなく、むしろメモリには収まらないがハードドライブには収まるには小さすぎるファイルを扱っています。
私の最初の考えは、HDFStore
を使って大きなデータセットをディスクに保存し、必要な部分だけを分析のためにデータフレームに入れることです。他の人は、MongoDBをより使いやすい代替手段として挙げています。私の質問はこれです:
以下を達成するためのベストプラクティスワークフローは何ですか。
特に「ラージデータ」にパンダを使用している人からは、現実世界の例が非常に高く評価されます。
編集 - 私がこれをどのように機能させたいかの例:
これらの手順を実行するためのベストプラクティス方法を見つけようとしています。パンダやpytablesに関するリンクを読むと、新しいコラムを追加することが問題になる可能性があるようです。
編集 - 特にジェフの質問に答える:
if var1 > 2 then newvar = 'A' Elif var2 = 4 then newvar = 'B'
です。これらの操作の結果、データセット内のすべてのレコードに対して新しい列ができます。データセットに行を追加することはめったにありません。私はほとんど常に新しいコラム(統計学/機械学習の専門用語における変数や機能)を作成します。
私は日常的にこのように数十ギガバイトのデータを使っています。私は、クエリを介して読み取り、データを作成し、そして追加するテーブルをディスク上に持っています。
データの保存方法に関するいくつかの提案については、 docs および このスレッドの後半 )を読む価値があります。
次のように、データの保存方法に影響する詳細情報
あなたができる限り詳細に説明します。そして私はあなたが構造を開発するのを手助けすることができます。
あなたが 少なくとも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'])
これらは、最終的なレポート生成段階で最も興味深いことがあります(基本的にデータ列は他の列から分離されているため、多くの定義をした場合、効率に多少影響する可能性があります)。
あなたはまたしたいかもしれません:
質問がある場合はお知らせください。
私は上記の答えが私が非常に有用であると思った簡単なアプローチを欠いていると思う。
大きすぎてメモリにロードできないファイルがある場合は、そのファイルを複数の小さいファイルに分割します(行または列によって)。
例:30日分の30 GBまでのサイズの取引データの場合、1日に1 GBまでのサイズのファイルに分割します。その後、それぞれのファイルを別々に処理し、最後に結果を集計します。
最大の利点の1つは、ファイルの並列処理(複数のスレッドまたはプロセス)を可能にすることです。
もう1つの利点は、ファイル操作(例では日付の追加/削除など)が通常のShellコマンドで実行できることです。これは、より高度で複雑なファイル形式では不可能です。
このアプローチはすべてのシナリオを網羅しているわけではありませんが、多くのシナリオで非常に役立ちます。
質問の2年後に、現在、「アウトオブコア」パンダと同等のものがあります: dask 。素晴らしいですね!それはパンダの機能のすべてをサポートするわけではありませんが、あなたは本当にそれを得ることができます。
データセットが1〜20GBの場合は、48GBのRAMを搭載したワークステーションを入手する必要があります。その後、Pandasはデータセット全体をRAMに保持できます。ここで探している答えではないことを私は知っていますが、4GBのRAMを搭載したノートブックで科学計算を行うのは合理的ではありません。
これは古いスレッドですが、 Blaze ライブラリはチェックする価値があると思います。それはこのような状況のために作られています。
ドキュメントより:
Blazeは、NumPyとPandasの使いやすさを、分散コンピューティングとアウトオブコアコンピューティングに拡張します。 Blazeは、NumPy ND-ArrayやPandas DataFrameに似たインターフェースを提供しますが、これらのよく知られたインターフェースをPostgresやSparkのような他のさまざまな計算エンジンにマッピングします。
編集: ところで、NumPyの作者であるContinuumIOとTravis Oliphantによってサポートされています。
これはpymongoの場合です。私はまた、PythonでSQL Server、SQLite、HDF、ORM(SQLAlchemy)を使用してプロトタイプを作成しました。何よりもまずpymongoはドキュメントベースのDBなので、それぞれの人はドキュメント(属性のdict
)になるでしょう。多くの人々がコレクションを形成し、あなたは多くのコレクション(人々、株式市場、収入)を持つことができます。
pd.dateframe - > pymongo注:read_csv
でchunksize
を使用して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は私がただ仕事に就くのを助けます:)
私はこれを少し遅く見つけました、しかし私は同様の問題(住宅ローン前払いモデル)で働きます。私の解決策は、パンダの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にデータをロードできることです。あるいは、少なくとも、私は異種のテーブルをロードすることができませんでした。
大きなデータ のユースケースに役立つトリックの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
他の人が指摘したように、数年後に 'アウトオブコア'パンダと同等のものが出現しました: 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()
もう1つのバリエーション
パンダで行われる操作の多くはdbクエリとしても行うことができます(sql、mongo)
RDBMSまたはmongodbを使用すると、DBクエリで大量の集計を実行できます(大規模データ用に最適化されており、キャッシュとインデックスを効率的に使用できます)。
後で、パンダを使用して後処理を実行できます。
この方法の利点は、ロジックを高レベルの宣言型構文で定義しながら、大きなデータを処理するためのDBの最適化が得られることです。メモリ内で何をして何をするかを決定する詳細を扱う必要がありませんコアの。
クエリ言語とパンダは異なりますが、ロジックの一部を相互に変換するのは通常複雑ではありません。
ここで言及する価値があります Ray
これは分散計算フレームワークであり、パンダのための独自の実装が分散的に行われています。
パンダのインポートを置き換えるだけで、コードはそのまま機能するはずです。
# import pandas as pd
import ray.dataframe as pd
#use pd as usual
ここで詳細を読むことができます:
Ruffus を考えてみましょう。単純な方法で複数の小さなファイルに分割されたデータパイプラインを作成する場合。
私は最近同様の問題に遭遇しました。私は、データをまとまりとして読んで、同じ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)
私はVaexパッケージを指摘したいのですが。
Vaexは、大きな表形式のデータセットを視覚化して探索するための、怠惰なOut-of-Core DataFrame(Pandasに似た)用のpythonライブラリです。平均、合計、数、標準偏差などの統計を、最大10億までのN次元グリッド上で計算できます(1091秒あたりのオブジェクト/行数。ヒストグラム、密度プロット、および3Dボリュームレンダリングを使用して視覚化が行われるため、ビッグデータをインタラクティブに探索できます。 Vaexは、最高のパフォーマンスを得るために、メモリマッピング、ゼロメモリコピーポリシー、および遅延計算を使用します(メモリを無駄にすることはありません)。
ドキュメントを見てください: https://vaex.readthedocs.io/en/latest/ APIはパンダのAPIに非常に近いです。