ここでは、pandas DataFrame のドキュメントから始めます。データ構造の紹介
時系列のような計算で、DataFrame に値を繰り返し入力したいと思います。DataFrame を列 A、B、タイムスタンプ行、すべて 0、またはすべて NaN で初期化したいと思います。
row[A][t] = row[A][t-1]+1
次に、初期値を追加し、このデータを調べて、たとえば前の行から新しい行を計算します。
現在、以下のようなコードを使用していますが、見た目が醜いので、これを DataFrame で直接実行する方法、または一般的にもっと良い方法があるはずです。
import pandas as pd
import datetime as dt
import scipy as s
base = dt.datetime.today().date()
dates = [ base - dt.timedelta(days=x) for x in range(9, -1, -1) ]
valdict = {}
symbols = ['A','B', 'C']
for symb in symbols:
valdict[symb] = pd.Series( s.zeros(len(dates)), dates )
for thedate in dates:
if thedate > dates[0]:
for symb in valdict:
valdict[symb][thedate] = 1 + valdict[symb][thedate - dt.timedelta(days=1)]
ベストアンサー1
DataFrame を行方向に拡張しないでください。
TLDR: (太字部分だけ読んでください)
ここでの回答のほとんどは、空の DataFrame を作成してそれを入力する方法を教えてくれますが、それを実行するのは悪いことだと教えてくれる人はいません。
私のアドバイスは次のとおりです: DataFrame ではなくリストにデータを蓄積します。
リストを使用してデータを収集し、準備ができたらDataFrameを初期化します。リストのリスト形式または辞書のリスト形式のどちらでも機能します。pd.DataFrame
どちらも受け入れます。
data = []
for row in some_function_that_yields_data():
data.append(row)
df = pd.DataFrame(data)
pd.DataFrame
行のリスト(各行はスカラー値)をDataFrameに変換します。関数がDataFrame
代わりにsを返す場合は、pd.concat
。
このアプローチの利点:
空の DataFrame (または NaN の 1 つ) を作成してそれに何度も追加するよりも、リストに追加して DataFrame を一度に作成した方が常にコストが安くなります。
リストはメモリの消費量も少なく、操作、追加、削除(必要な場合)がはるかに簡単なデータ構造です。
dtypes
自動的に推論されます(object
すべてに割り当てるのではなく)。RangeIndex
各反復で追加する行に正しいインデックスを割り当てるように注意する必要はなく、データに対してが自動的に作成されます。
まだ納得していないなら、これについても言及されている。ドキュメンテーション:
DataFrame に行を繰り返し追加すると、単一の連結よりも計算負荷が大きくなる可能性があります。よりよい解決策は、それらの行をリストに追加し、そのリストを元の DataFrame と一度に連結することです。
pandas >= 2.0 アップデート:append
削除されました!
DataFrame.append
だったバージョン 1.4 では非推奨そしてバージョン2.0ではpandas APIから完全に削除されました参照このgithubの問題当初は廃止を提案していました。
これらのオプションはひどい
append
またはconcat
ループ内
私が見た初心者の最大の間違いは次のとおりです。
df = pd.DataFrame(columns=['A', 'B', 'C'])
for a, b, c in some_function_that_yields_data():
df = df.append({'A': i, 'B': b, 'C': c}, ignore_index=True) # yuck
# or similarly,
# df = pd.concat([df, pd.Series({'A': i, 'B': b, 'C': c})], ignore_index=True)
append
または操作ごとにメモリが再割り当てされますconcat
。これをループと組み合わせると、2 次複雑度の操作になります。
に関連するもう 1 つの間違いは、ユーザーがappend がインプレース関数ではないことdf.append
を忘れがちであるため、結果を再度割り当てる必要があることです。また、dtype についても考慮する必要があります。
df = pd.DataFrame(columns=['A', 'B', 'C'])
df = df.append({'A': 1, 'B': 12.3, 'C': 'xyz'}, ignore_index=True)
df.dtypes
A object # yuck!
B float64
C object
dtype: object
オブジェクト列を扱うことは決して良いことではありません。なぜなら、パンダはそれらの列に対する操作をベクトル化できないからです。infer_objects()
修正方法:
df.infer_objects().dtypes
A int64
B float64
C object
dtype: object
loc
ループ内
loc
空で作成された DataFrame に追加するために使用されているのも見ました。
df = pd.DataFrame(columns=['A', 'B', 'C'])
for a, b, c in some_function_that_yields_data():
df.loc[len(df)] = [a, b, c]
以前と同様に、必要なメモリ量を毎回事前割り当てしていないため、新しい行を作成するたびにメモリが再拡張されます。 と同じくらい悪いだけでなくappend
、さらに醜いです。
NaN の空のデータフレーム
そして、NaN の DataFrame を作成すると、それに関連するすべての注意事項が発生します。
df = pd.DataFrame(columns=['A', 'B', 'C'], index=range(5))
df
A B C
0 NaN NaN NaN
1 NaN NaN NaN
2 NaN NaN NaN
3 NaN NaN NaN
4 NaN NaN NaN
他のものと同様に、列の DataFrame を作成しますobject
。
df.dtypes
A object # you DON'T want this
B object
C object
dtype: object
追加には、上記の方法と同様に、依然としてすべての問題が残ります。
for i, (a, b, c) in enumerate(some_function_that_yields_data()):
df.iloc[i] = [a, b, c]
証拠は実績の中にある
これらのメソッドの時間を計測することが、メモリとユーティリティの点でどれだけ異なるかを確認する最も早い方法です。