永远不要增长 DataFrame!
TLDR; (只需阅读粗体字)
这里的大多数答案会告诉你如何创建一个空的 DataFrame 并填写它,但没有人会告诉你这是一件坏事。
这是我的建议:将数据累积到列表中,而不是 DataFrame。
使用列表收集数据,然后在准备好时初始化 DataFrame。 list-of-lists 或 list-of-dicts 格式都可以使用,pd.DataFrame 两者都接受。
data = []
for a, b, c in some_function_that_yields_data():
data.append([a, b, c])
df = pd.DataFrame(data, columns=['A', 'B', 'C'])
这种方法的优点:
-
与创建一个空的 DataFrame(或 NaN 之一)并一遍又一遍地附加到它相比,添加到列表并一次性创建一个 DataFrame 总是更便宜。
-
列表占用的内存也更少,并且是一种更轻便的数据结构,可以使用、追加和删除(如果需要)。
-
dtypes 是自动推断出来的(而不是将object 分配给所有这些)。
-
会自动为您的数据创建RangeIndex,您不必小心为每次迭代时附加的行分配正确的索引。
如果你还不服气,documentation中也提到了这一点:
迭代地将行附加到 DataFrame 可以在计算上更加高效
比单个连接更密集。更好的解决方案是追加
这些行到一个列表,然后将列表与原始列表连接起来
一次全部使用 DataFrame。
但是,如果我的函数返回需要合并成一个大数据帧的较小数据帧怎么办?
没关系,您仍然可以通过增长或创建较小 DataFrames 的 python 列表,然后调用 pd.concat,在线性时间内完成此操作。
small_dfs = []
for small_df in some_function_that_yields_dataframes():
small_dfs.append(small_df)
large_df = pd.concat(small_dfs, ignore_index=True)
或者,更简洁:
large_df = pd.concat(
list(some_function_that_yields_dataframes()), ignore_index=True)
这些选项太可怕了
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 操作重新分配。将此与循环结合起来,您就有了二次复杂度运算。
与df.append 相关的另一个错误是用户倾向于忘记append 不是就地函数,因此必须将结果分配回。您还必须担心 dtypes:
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
处理对象列从来都不是一件好事,因为 pandas 无法对这些列进行矢量化操作。你需要这样做来修复它:
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 的空 DataFrame
然后,创建一个包含 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,就像其他列一样。
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]
证据就在布丁里
对这些方法进行计时是了解它们在内存和实用性方面的差异的最快方法。
Benchmarking code for reference.