创建一个空的 Pandas DataFrame,然后填充它
- 2024-12-13 08:36:00
- admin 原创
- 128
问题描述:
我从这里的 pandas DataFrame 文档开始:数据结构简介
我想在时间序列类型的计算中迭代地用值填充 DataFrame。我想用列 A、B 和时间戳行初始化 DataFrame,全部为 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 总是比创建一个空的 DataFrame(或 NaN 之一)并一遍又一遍地附加到其中更便宜。
列表还占用更少的内存,并且是一种更轻量的数据结构,可用于处理、附加和删除(如果需要)。
dtypes
都是自动推断的(而不是分配object
给所有)。RangeIndex
系统会自动为您的数据创建A,而您无需在每次迭代时小心地为要附加的行分配正确的索引。
如果你还不相信的话,文档中也提到了这一点:
迭代地将行附加到 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
。将其与循环结合起来,您就会得到一个二次复杂度的运算。
与之相关的另一个错误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 无法对这些列上的操作进行矢量化。你需要调用该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
,甚至更丑陋。
空的 DataFrame 包含 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
object
它像其他的一样创建一个由列组成的 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]
实践才是检验的依据
对这些方法进行计时是了解它们在内存和实用性方面差异的最快方法。
供参考的基准代码。
解决方案 2:
以下是一些建议:
用于date_range
索引:
import datetime
import pandas as pd
import numpy as np
todays_date = datetime.datetime.now().date()
index = pd.date_range(todays_date-datetime.timedelta(10), periods=10, freq='D')
columns = ['A','B', 'C']
注意:我们可以通过简单的编写来创建一个空的 DataFrame(带有NaN
s):
df_ = pd.DataFrame(index=index, columns=columns)
df_ = df_.fillna(0) # With 0s rather than NaNs
要对数据执行这些类型的计算,请使用NumPy数组:
data = np.array([np.arange(10)]*3).T
因此我们可以创建 DataFrame:
In [10]: df = pd.DataFrame(data, index=index, columns=columns)
In [11]: df
Out[11]:
A B C
2012-11-29 0 0 0
2012-11-30 1 1 1
2012-12-01 2 2 2
2012-12-02 3 3 3
2012-12-03 4 4 4
2012-12-04 5 5 5
2012-12-05 6 6 6
2012-12-06 7 7 7
2012-12-07 8 8 8
2012-12-08 9 9 9
解决方案 3:
如果您只是想创建一个空数据框并稍后用一些传入数据框填充它,请尝试以下操作:
newDF = pd.DataFrame() #creates a new dataframe that's empty
newDF = newDF.append(oldDF, ignore_index = True) # ignoring index is optional
# try printing some data from newDF
print newDF.head() #again optional
在这个例子中,我使用这个 pandas 文档来创建一个新的数据框,然后使用append将来自 oldDF 的数据写入 newDF。
如果我必须继续将来自多个 oldDF 的新数据附加到这个 newDF 中,我只需使用 for 循环遍历
pandas.DataFrame.append()
注意:append()
自 1.4.0 版本起已弃用。使用concat()
。
解决方案 4:
使用列名初始化空框架
import pandas as pd
col_names = ['A', 'B', 'C']
my_df = pd.DataFrame(columns = col_names)
my_df
向框架添加新记录
my_df.loc[len(my_df)] = [2, 4, 5]
您可能还想传递一本字典:
my_dic = {'A':2, 'B':4, 'C':5}
my_df.loc[len(my_df)] = my_dic
将另一个框架附加到现有框架
col_names = ['A', 'B', 'C']
my_df2 = pd.DataFrame(columns = col_names)
my_df = my_df.append(my_df2)
性能注意事项
如果在循环中添加行,请考虑性能问题。对于前 1000 条记录,“my_df.loc” 的性能较好,但随着循环中记录数量的增加,性能会逐渐变慢。
如果您打算在一个大循环中执行细化操作(例如 10M 条记录左右),最好将这两种方法混合使用;使用 iloc 填充数据框,直到大小达到 1000 左右,然后将其附加到原始数据框,并清空临时数据框。这将使您的性能提高约 10 倍。
解决方案 5:
简单地:
import numpy as np
import pandas as pd
df=pd.DataFrame(np.zeros([rows,columns])
然后填充。
解决方案 6:
假设一个数据框有 19 行
index=range(0,19)
index
columns=['A']
test = pd.DataFrame(index=index, columns=columns)
将 A 列保持为常量
test['A']=10
将 b 列保留为循环给出的变量
for x in range(0,19):
test.loc[[x], 'b'] = pd.Series([x], index = [x])
pd.Series([x], index = [x])
你可以用任意值替换第一个 x
解决方案 7:
这是我使用循环从多个列表创建动态数据框的方法
x = [1,2,3,4,5,6,7,8]
y = [22,12,34,22,65,24,12,11]
z = ['as','ss','wa', 'ss','er','fd','ga','mf']
names = ['Bob', 'Liz', 'chop']
循环
def dataF(x,y,z,names):
res = []
for t in zip(x,y,z):
res.append(t)
return pd.DataFrame(res,columns=names)
结果
dataF(x,y,z,names)
解决方案 8:
Pandas 数据框可以看作是 Pandas 列的字典(Pandas 系列)。就像字典中添加新的键值对的成本很低一样,添加新的列/列也非常高效(数据框就是这样水平增长的)。
df = pd.DataFrame()
df['A'] = range(0, 2000_000, 2) # add one column
df[['B', 'C']] = ['a', 'b'] # add multiple columns
另一方面,就像更新字典中的每个值都需要循环遍历整个字典一样,通过添加新行来垂直扩大数据框的效率非常低。如果在循环中逐行添加新行,则效率尤其低下(请参阅此帖子,了解比较可能选项的基准)。
如果新行值依赖于 OP 中的前一行值,那么根据列数,最好循环遍历预先初始化的零数据框,或者在循环中增加 Python 字典,然后构造数据框(如果有超过 500 列,最好循环遍历数据框)。但将两者混合使用永远不是最佳选择,换句话说,增加 pandas Series 字典将非常慢。1
dates = pd.date_range(end=pd.Timestamp('now'), periods=10000, freq='D').date
symbols = [f"col{i}" for i in range(10)]
# initialize a dataframe
df = pd.DataFrame(0, index=dates, columns=symbols)
# update it in a loop
for i, thedate in enumerate(df.index):
if thedate > df.index[0]:
df.loc[thedate] = df.loc[df.index[i-1]] + 1
# build a nested dictionary
data = {}
for i, thedate in enumerate(dates):
for symb in symbols:
if thedate > dates[0]:
data[symb][thedate] = 1 + data[symb][dates[i-1]]
else:
data[symb] = {thedate: 0}
# construct a dataframe after
df1 = pd.DataFrame(data)
1:也就是说,对于这个特定的例子,cumsum()
甚至range()
似乎不需要循环遍历行就可以工作。答案的这一部分更多地是关于循环不可避免的情况,例如财务数据操作等。