在Python Pandas数据框中以低成本添加时间序列强度的方法
我正在尝试用Python的Pandas.DataFrame来计算(并绘制)一些函数在不同时间改变状态的总和。举个例子:
假设我们有3个人,他们的状态可以是:a) 什么都不拿,b) 拿着一个5磅的重物,c) 拿着一个10磅的重物。随着时间的推移,这些人会拿起重物或放下重物。我想要绘制他们所持重物的总重量。因此,给定:
我最初的尝试:
import pandas as ps
import math
import numpy as np
person1=[3,0,10,10,10,10,10]
person2=[4,0,20,20,25,25,40]
person3=[5,0,5,5,15,15,40]
allPeopleDf=ps.DataFrame(np.array(zip(person1,person2,person3)).T)
allPeopleDf.columns=['count','start1', 'end1', 'start2', 'end2', 'start3','end3']
allPeopleDfNoCount=allPeopleDf[['start1', 'end1', 'start2', 'end2', 'start3','end3']]
uniqueTimes=sorted(ps.unique(allPeopleDfNoCount.values.ravel()))
possibleStates=[-1,0,1,2] #extra state 0 for initialization
stateData={}
comboStates={}
#initialize dict to add up all of the stateData
for time in uniqueTimes:
comboStates[time]=0.0
allPeopleDf['track']=-1
allPeopleDf['status']=-1
numberState=len(possibleStates)
starti=-1
endi=0
startState=0
for i in range(3):
starti=starti+2
print starti
endi=endi+2
for time in uniqueTimes:
def helper(row):
start=row[starti]
end=row[endi]
track=row[7]
if start <= time and time < end:
return possibleStates[i+1]
else:
return possibleStates[0]
def trackHelp(row):
status=row[8]
track=row[7]
if track<=status:
return status
else:
return track
def Multiplier(row):
x=row[8]
if x==0:
return 0.0*row[0]
if x==1:
return 5.0*row[0]
if x==2:
return 10.0*row[0]
if x==-1:#numeric place holder for non-contributing
return 0.0*row[0]
allPeopleDf['status']=allPeopleDf.apply(helper,axis=1)
allPeopleDf['track']=allPeopleDf.apply(trackHelp,axis=1)
stateData[time]=allPeopleDf.apply(Multiplier,axis=1).sum()
for k,v in stateData.iteritems():
comboStates[k]=comboStates.get(k,0)+v
print allPeopleDf
print stateData
print comboStates
随着时间的推移,所持重物的图像可能看起来像这样:
而随时间变化的强度总和可能看起来像下面的黑线:
这条黑线的坐标点定义为:(0,0磅),(5,0磅),(5,5磅),(15,5磅),(15,10磅),(20,10磅),(20,15磅),(25,15磅),(25,20磅),(40,20磅)。不过,我比较灵活,不一定要把这个总强度线定义为一组坐标点。可以通过以下方式找到独特的时间点: print list(set(uniqueTimes).intersection(allNoCountT[1].values.ravel())).sort() ,但我想不出一个好的方法来获取对应的强度值。
我一开始用一个非常复杂的函数来拆分每个人的图表,这样所有人都有相同的开始和结束时间(尽管有很多没有状态变化的开始和结束时间),然后我可以把所有的“时间块”加起来。这很麻烦;一定有更简洁的Pandas方法来处理这个。如果有人能给我建议或者指向我可能错过的其他相关内容,我会很感激!
如果我这个简单的例子不够清楚,另一个例子可能是绘制钢琴发出的声音强度:有很多音符在不同的时间以不同的强度被演奏。我想要的是钢琴在一段时间内的强度总和。虽然我的例子很简单,但我需要一个更适合钢琴曲的解决方案:每个音键有成千上万的离散强度级别,并且在一首曲子中有很多音键在贡献。
编辑--实现mgab提供的解决方案:
import pandas as ps
import math
import numpy as np
person1=['person1',3,0.0,10.0,10.0,10.0,10.0,10.0]
person2=['person2',4,0,20,20,25,25,40]
person3=['person3',5,0,5,5,15,15,40]
allPeopleDf=ps.DataFrame(np.array(zip(person1,person2,person3)).T)
allPeopleDf.columns=['id','intensity','start1', 'end1', 'start2', 'end2', 'start3','end3']
allPeopleDf=ps.melt(allPeopleDf,id_vars=['intensity','id'])
allPeopleDf.columns=['intensity','id','timeid','time']
df=ps.DataFrame(allPeopleDf).drop('timeid',1)
df[df.id=='person1'].drop('id',1) #easier to visualize one id for check
df['increment']=df.groupby('id')['intensity'].transform( lambda x: x.sub(x.shift(), fill_value= 0 ))
TypeError: unsupported operand type(s) for -: 'str' and 'int'
结束编辑
2 个回答
看起来这就是.sum()
的作用:
In [10]:
allPeopleDf.sum()
Out[10]:
aStart 0
aEnd 35
bStart 35
bEnd 50
cStart 50
cEnd 90
dtype: int32
以钢琴键为例,假设你有三个键,每个键有30个强度级别。
我会尝试把数据保持在这样的格式:
import pandas as pd
df = pd.DataFrame([[10,'A',5],
[10,'B',7],
[13,'C',10],
[15,'A',15],
[20,'A',7],
[23,'C',0]], columns=["time", "key", "intensity"])
time key intensity
0 10 A 5
1 10 B 7
2 13 C 10
3 15 A 15
4 20 A 7
5 23 C 0
在这里,你记录每个键的强度变化。这样你就可以得到每个键的坐标,格式是(时间, 强度)
的组合。
df[df.key=="A"].drop('key',1)
time intensity
0 10 5
3 15 15
4 20 7
接着,你可以很容易地创建一个新的列增量
,这个列会显示在某个时间点该键的强度变化(强度
表示新的强度值)。
df["increment"]=df.groupby("key")["intensity"].transform(
lambda x: x.sub(x.shift(), fill_value= 0 ))
df
time key intensity increment
0 10 A 5 5
1 10 B 7 7
2 13 C 10 10
3 15 A 15 10
4 20 A 7 -8
5 23 C 0 -10
然后,利用这个新列,你可以生成(时间, 总强度)
的组合,作为坐标使用。
df.groupby("time").sum()["increment"].cumsum()
time
10 12
13 22
15 32
20 24
23 14
dtype: int64
编辑:应用问题中提供的具体数据
假设数据是以值的列表形式出现,首先是元素的ID(人或钢琴键),然后是一个乘以测量重量/强度的因子,接着是一对对的时间值,表示一系列已知状态的开始和结束(比如承载的重量/发出的强度)。 不确定我是否理解了数据格式。根据你的问题:
data1=['person1',3,0.0,10.0,10.0,10.0,10.0,10.0]
data2=['person2',4,0,20,20,25,25,40]
data3=['person3',5,0,5,5,15,15,40]
如果我们知道每个状态的重量/强度,我们可以定义:
known_states = [5, 10, 15]
DF_columns = ["time", "id", "intensity"]
然后,我想到的加载数据的最简单方法是这个函数:
import pandas as pd
def read_data(data, states, columns):
id = data[0]
factor = data[1]
reshaped_data = []
for i in xrange(len(states)):
j += 2+2*i
if not data[j] == data[j+1]:
reshaped_data.append([data[j], id, factor*states[i]])
reshaped_data.append([data[j+1], id, -1*factor*states[i]])
return pd.DataFrame(reshaped_data, columns=columns)
注意if not data[j] == data[j+1]:
这段代码可以避免在数据框中加载当某个状态的开始和结束时间相等时的数据(这似乎没有信息量,而且在你的图表中也不会出现)。不过如果你还想保留这些条目,可以把这段代码去掉。
接下来,你就可以加载数据了:
df = read_data(data1, known_states, DF_columns)
df = df.append(read_data(data2, known_states, DF_columns), ignore_index=True)
df = df.append(read_data(data3, known_states, DF_columns), ignore_index=True)
# and so on...
然后你就可以回到这个回答的开头(把'键'替换成'id'和相应的id,当然)。