在工作时,我遇到了一些关于Pandas DF和append的奇怪行为。
目标是生成具有RowNum
行数的DF,重复UniqueInt
值,并生成从UniqueInt
+1到RowNum
的连续数字的新列。这是一种用以前相同的数据填充中间的空数据的方法。
我主要关心的不是如何实现这一点,而是为什么在将结果附加到空数据帧时,结果数据与代码的输出不一致(即:附加是更改所附加数据的值(。
这是在Python 3.7.4上。
我创建了一个非常小的可复制示例:
import pandas as pd
import numpy as np
#Create a DF
TemporalDF=pd.DataFrame([2,2,3,3,3,7,7,7,8,8,8,9,9,10,10,10])
TemporalDF.columns=['Int']
#Create recipients for data
BuggedResult=[]
CorrectResult=pd.DataFrame()
# For loop
for UniqueInt in range(TemporalDF['Int'].unique()[0],10):
# Specify desired number of rows
RowNum=(10-UniqueInt)
# Subset original data
Temp=TemporalDF[TemporalDF['Int']==UniqueInt]
# Fill gaps of data based on last correctly recorded data
if(Temp.shape[0]==0):
# Take last recorded value
DummyDF=DummyDF.iloc[1:DummyDF.shape[0]+1,:]
DummyDF['FillIntStart']=np.repeat(a=UniqueInt, repeats=RowNum)
else:
# Create empty data frame
DummyDF=pd.DataFrame()
# Populate
DummyDF['FillIntStart']=np.repeat(a=UniqueInt, repeats=RowNum)
DummyDF['FillIntEnd']=[UniqueInt+i for i in range(1,RowNum+1)]
# Save results
BuggedResult.append(DummyDF)
CorrectResult=CorrectResult.append(other=DummyDF, ignore_index=True)
pass
使用此代码,您可以看到有两种存储数据的方法:
- 使用
BuggedResult.append()
- 采用Pandas的
pd.append()
方法
BuggedResult
(BuggedResult[0]
(数组的第一个元素正常,看起来像这样:
┌──────────────┬────────────┐
│ FillIntStart │ FillIntEnd │
├──────────────┼────────────┤
│ 2 │ 3 │
│ 2 │ 4 │
│ 2 │ 5 │
│ 2 │ 6 │
│ 2 │ 7 │
│ 2 │ 8 │
│ 2 │ 9 │
│ 2 │ 10 │
└──────────────┴────────────┘
但第二个元素(BuggedResult[1]
(看起来是这样的:
┌──────────────┬────────────┐
│ FillIntStart │ FillIntEnd │
├──────────────┼────────────┤
│ 3 │ 4 │
│ 4 │ 5 │
│ 5 │ 6 │
│ 6 │ 7 │
│ 6 │ 8 │
│ 6 │ 9 │
│ 6 │ 10 │
└──────────────┴────────────┘
当它看起来像这样时(取自CorrecResult表,使用pd.append()
(:
┌──────────────┬────────────┐
│ FillIntStart │ FillIntEnd │
├──────────────┼────────────┤
│ 3 │ 4 │
│ 3 │ 5 │
│ 3 │ 6 │
│ 3 │ 7 │
│ 3 │ 8 │
│ 3 │ 9 │
│ 3 │ 10 │
└──────────────┴────────────┘
换句话说,append方法是在我附加数据后更改我的数据。如果你检查代码,你也可以尝试我已经尝试过的几件事,比如手动跟随循环,添加DummyDF.to_txt()
方法来读取单独文件中的数据,等等。逻辑似乎还可以,但当我将其附加到空数组时,结果会发生变化。
这是Python 3.7.4的一些奇怪的预期行为吗?可能不建议在空数组中添加DF,因为Panda已经有了解决方案,但我认为更改数据太多了。
我真诚地希望这个问题是我的,因为我不是Python专家。。。那么,对此有什么想法吗?
谢谢!
以下是我的操作方法:
>>> temporal = np.array([2,2,3,3,3,7,7,7,8,8,8,9,9,10,10,10])
>>> max_temporal = np.max(temporal)
>>> result = []
>>> columns = ['FillIntStart', 'FillIntEnd']
>>> for x in np.unique(temporal):
... start = np.repeat(x, max_temporal - x)
... end = np.arange(x + 1, max_temporal + 1)
... result.append(pd.DataFrame({columns[0]: start, columns[1]: end}, columns=columns))
...
>>> result = pd.concat(result)
>>> print(result.to_string(index=False))
FillIntStart FillIntEnd
2 3
2 4
2 5
2 6
2 7
2 8
2 9
2 10
3 4
3 5
3 6
3 7
3 8
3 9
3 10
7 8
7 9
7 10
8 9
8 10
9 10
如果我理解的话,这就是你想要实现的结果。
我必须仔细查看您的代码,以了解它有什么问题。特别是,我不清楚这部分到底在做什么:
# Fill gaps of data based on last correctly recorded data
if(Temp.shape[0]==0):
# Take last recorded value
DummyDF=DummyDF.iloc[1:DummyDF.shape[0]+1,:]
DummyDF['FillIntStart']=np.repeat(a=UniqueInt, repeats=RowNum)
此代码一开始就有缺陷,因为DummyDF
在运行时可能尚未定义(仅当else:
块在前一个循环中运行时(。我有点不清楚在这种情况下你想做什么,因为它似乎是在处理[2,10]范围内的缺失值,而这些值不在你的原始TemporalDF
中,我认为你没有解释你在那种情况下想做什么。您重用以前循环中的DummyDF
是导致错误的原因。当我在pdb中逐步查看您的代码(调试自己的代码是一项值得学习的技能(时,我发现这里发生了什么:由于您正在原地修改DataFrame
,您随后的循环最终会修改已经在BuggedResult
列表中的同一个DataFrame
实例。DataFrame.append
不会出现这个问题,因为它将数据复制到CorrectResult
中,并在此过程中调整其数据缓冲区的大小。
如果可能的话,我会尽量避免使用DataFrame.append
——在这里,像我的例子中那样使用单个pd.concat
更有效,因为它可以为所有输出构建一个正确大小的单个DataFrame
,然后复制到其中一次,而不是调整每个循环上的数据大小。也许有一个更好的解决方案,但目前还没有想到。
(顺便说一句,Python有一个关于如何格式化代码的风格指南PEP 8,它建议变量名使用小写,而CamelCase通常是为类名保留的。当然,没有要求你使用它,一致性最重要。但大多数Python社区都试图遵守这些约定,所以阅读不这样做的代码有点不舒服(。