创建数据框的正确方法
TLDR;(只需阅读粗体文字)
这里的大多数答案将告诉您如何创建一个空的DataFrame并将其填写,但是没有人会告诉您这是一件坏事。
这是我的建议:等待直到您确定拥有所有需要使用的数据。使用列表收集数据,然后在准备好时初始化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总是比创建一个空的DataFrame(或NaN之一)便宜,一遍又一遍地添加到列表。列表还占用较少的内存,并且可以轻松处理,追加和删除(如果需要)的数据结构。
此方法的另一个优点是dtypes
可以自动推断(而不是分配object
给所有对象)。
最后一个优点是为您的数据自动创建了aRangeIndex
,因此不必担心(只需查看下面的劣势append
和loc
方法,您将在两种方法中看到需要正确处理索引的元素)。
你不应该做的事情
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
文档页面:
迭代地将行添加到DataFrame可能比单个连接更多地占用大量计算资源。更好的解决方案是将这些行添加到列表中,然后一次将列表与原始DataFrame连接起来。
与之相关的另一个错误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
处理对象列从来都不是一件好事,因为熊猫无法向量化这些列上的操作。您将需要执行以下操作来修复它:
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。
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]
证明在布丁中
对这些方法进行计时是最快的方法,以了解它们在内存和实用性方面的差异。
基准测试代码,以供参考。