创建一个空的Pandas DataFrame,然后填充它。

849
我从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)]

67
永远不要让DataFrame增长!从内存和性能两方面考虑,将数据附加到Python列表中,然后在最后将其转换为DataFrame的成本更低廉。 - cs95
1
@cs95 在 pd 中使用 .append 和在 Python 中使用列表添加之间的功能区别是什么?我知道 pandas 中的 .append 会将整个数据集复制到一个新对象中,那么 Python 的 append 是否有不同的工作方式呢? - Lamma
4
@Lamma,请查看下面我的回答中的细节。在将内容追加到df时,每次都会在内存中创建一个新的DataFrame,而不是使用现有的DataFrame,这实际上是一种浪费。 - cs95
2
append现已正式弃用。请参考https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.append.html。 - rubengavidia0x
9个回答

944

绝对不要逐行增加DataFrame!

简而言之;(只读粗体文字)

这里的大多数答案都会告诉你如何创建一个空的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,请调用{{link1:pd.concat}}。

这种方法的优点:

  1. 将数据附加到列表并一次性创建DataFrame比创建一个空的DataFrame(或NaN的DataFrame)并反复附加要便宜得多。

  2. 列表占用的内存也较少,是一种更轻量级的数据结构,可以更容易地进行附加和删除(如果需要)。

  3. 自动推断dtypes(而不是将object分配给所有dtypes)。

  4. 为您的数据自动创建RangeIndex,而不必在每次迭代时手动分配正确的索引给附加的行。

如果你还不相信的话,这也在文档中提到了:
逐行追加到DataFrame中的操作比单次连接更消耗计算资源。更好的解决方案是将这些行追加到一个列表中,然后一次性将列表与原始DataFrame连接起来。 pandas >= 2.0 更新:`append`已被移除!
`DataFrame.append`在1.4版本中被弃用,并在2.0版本中完全从pandas API中移除。
请参阅弃用文档以及最初提出其弃用的github问题

这些选项太糟糕了。
在循环中使用appendconcat是一个很大的错误。
以下是我从初学者那里看到的最大的错误之一:
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)

每次进行appendconcat操作时,都会重新分配内存。再加上循环,就会产生二次复杂度操作
df.append相关的另一个错误是用户往往忘记append不是一个原地函数,所以结果必须重新赋值。您还需要关注数据类型:
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`一样糟糕,甚至更丑陋。

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

它创建了一个包含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]


实践出真知

通过计时这些方法,可以最快地看出它们在内存和实用性方面的差异有多大。

enter image description here

参考用的基准代码。


39
这个文字字面上就在文档中。"通过迭代地将行附加到DataFrame中可能比单个连接更耗费计算资源。更好的解决方案是将这些行附加到一个列表中,然后一次性将列表与原始DataFrame连接起来。" https://pandas.pydata.org/pandas-docs/version/0.21/generated/pandas.DataFrame.append.html - endolith
2
此外,“值得注意的是,concat()函数(因此也包括append())会完全复制数据,并且反复使用此函数可能会对性能造成重大影响。如果您需要在多个数据集上使用该操作,请使用列表推导式。” https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html#concatenating-objects - endolith
2
那么,当我的数据以一维列表的形式一个接一个地到达,每个列表代表数据帧中的一列时,我该怎么办?在将它们转换为数据帧之前,如何将它们附加在一起?似乎list1.append(list2)会将一个列表插入另一个列表中,而不是添加一列。谢谢。 - Confounded
3
这与此处所询问的问题不同,但是将一个列逐个赋值给一个空数据框应该是可以的。问题出现在连续地添加行时。 - cs95
1
@micstr,a、b和c是单独的原子标量值,而不是列表。如果您已经有数据列表可用,只需调用pd.DataFrame([a_list, b_list, c_list])即可。 - cs95
显示剩余20条评论

422
这里有几点建议:
使用 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):

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

2
pd.date_range() 对我没有作用。我尝试使用 DateRange(来自 Eclipse 的自动完成),但它只能使用字符串作为日期格式,对吗? 不过整体的方法还是可行的(我将索引更改为其他内容)。 - Matthias Kauer
3
date_range 是一个创建日期时间索引的工厂函数,它是 Pandas 0.8.0 中的 新功能。我强烈建议升级到最新的稳定版本(0.9.1),因为有很多 bug 修复和新特性。 :) - Andy Hayden
35
根据我的经验,创建一个大小适当、填充 NaN 值的数据框,然后再填充值,比创建一个 index x 0 维度(columns = [])的数据框,并在循环的每一轮中逐列附加要慢得多。我指的是在循环中使用 df[col_name] = pandas.Series([...])。在前一种情况下,不仅内存分配需要时间,而且用新值替换 NaN 值似乎也非常慢。 - deeenes
7
当然。这个答案可能应该更明确地表达——你很少(如果有的话)想要创建一个空的Dataframe(只包含NaN值)。 - Andy Hayden
1
根据这个答案 https://dev59.com/_4rda4cB1Zd3GeqPSej8#30267881 你需要分配fillna的结果,或传递参数inplace=True。 - JayJay
显示剩余2条评论

226
如果你只是想创建一个空的数据框,并在以后填充一些传入的数据框,可以尝试这样做:
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将旧数据框中的数据写入新的数据框newDF。
如果我需要从多个旧数据框中不断追加新数据到这个新数据框newDF中,我只需使用一个for循环遍历pandas.DataFrame.append()
注意:append()自版本1.4.0起已被弃用。请使用concat()

24
请注意,append(以及类似的concat)每次都会将完整数据集复制到一个新对象中,因此,迭代和添加会导致严重的性能问题。欲了解更多信息,请参阅:http://pandas.pydata.org/pandas-docs/stable/merging.html。 - MoustafaAAtta
4
追加数据到数据框的替代方法有哪些? - MysteryGuy
2
@MoustafaAAtta 在这篇帖子中的Fred回答是否在这个角度上更好: https://dev59.com/gGgv5IYBdhLWcg3wSe0f? - MysteryGuy
@MoustafaAAtta 你可以尝试将行附加到数据框中,它仍然会创建一个新对象,但对于较小的数据集可能很有用。https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html#appending-rows-to-a-dataframe - geekidharsh
1
请注意,append方法已被官方弃用,请查看文档: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.append.html - rubengavidia0x

167

初始化带有列名的空框架

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倍。


除非我指定 ignore_index=True,否则 my_df = my_df.append(my_df2) 对我无效。 - Nasif Imtiaz Ohi

2

简单来说:

import numpy as np
import pandas as pd

df=pd.DataFrame(np.zeros([rows,columns])

然后填写它。

对于等效的NaN初始化数组,请使用我编写的设备驱动程序! - Nicholas White

1
假设一个有19行的数据框。
index=range(0,19)
index

columns=['A']
test = pd.DataFrame(index=index, columns=columns)

保持A列不变
test['A']=10

保留HTML标签,翻译如下:

将列b保持为循环给定的变量

for x in range(0,19):
    test.loc[[x], 'b'] = pd.Series([x], index = [x])

您可以将pd.Series([x], index=[x])中的第一个x替换为任何值。


0

这是我使用循环从多个列表创建动态数据框的方法

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)

enter image description here


0
# import pandas library
import pandas as pd

# create a dataframe
my_df = pd.DataFrame({"A": ["shirt"], "B": [1200]})

# show the dataframe
print(my_df)

0
Pandas数据框可以被视为带有pandas列(pandas Series)的字典。就像添加新的键值对很便宜的字典一样,添加新的列/列非常高效(并且数据框旨在水平增长)。
df = pd.DataFrame()
df['A'] = range(0, 2000_000, 2)   # add one column
df[['B', 'C']] = ['a', 'b']       # add multiple columns

另一方面,就像更新字典的每个值需要循环整个字典一样,通过添加新行来垂直扩展数据框非常低效。如果在循环中逐个添加新行,则特别低效(请参见this post以比较可能的选项)。

如果新行的值取决于前一行的值(如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() 看起来都可以在不循环行的情况下工作。这部分回答更多地涉及到无法避免循环的情况,例如金融数据处理等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接