在 python pandas 数据框中添加时间序列强度的廉价方法

Posted

技术标签:

【中文标题】在 python pandas 数据框中添加时间序列强度的廉价方法【英文标题】:Inexpensive way to add time series intensity in python pandas dataframe 【发布时间】:2014-04-11 22:19:09 【问题描述】:

我正在尝试对使用 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 lbs),(5,0 lbs),(5,5 lbs),(15,5 lbs),(15,10 lbs),(20 ,10 磅),(20,15 磅),(25,15 磅),(25,20 磅),(40,20 磅)。但是,我很灵活,不一定需要将组合强度线定义为一组笛卡尔点。可以通过以下方式找到独特的时间: 打印列表(set(uniqueTimes).intersection(allNoCountT[1].values.ravel())).sort() ,但我想不出一个巧妙的方法来获取相应的强度值。

我开始使用一个非常丑陋的函数来分解每个“人”的图表,以便所有人同时拥有开始和停止时间(尽管有很多停止和开始时间没有状态改变),然后我可以加起来时间的所有“块”。这很麻烦。必须有一种巧妙的熊猫方式来处理这个问题。如果有人可以提供建议或将我指向另一个我可能错过的 SO,我将不胜感激!

如果我的简化示例不清楚,另一个可能是绘制来自钢琴的声音强度:有许多音符在不同的持续时间以不同的强度演奏。我想要随着时间的推移钢琴的强度总和。虽然我的示例很简单,但我需要一个更接近钢琴歌曲规模的解决方案:每个键有数千个离散强度级别,并且在歌曲过程中有许多键。

编辑--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: 不支持的操作数类型 -: 'str' 和 'int'

结束编辑

【问题讨论】:

什么是'start1'、'end1'等等?次?当权重/音符强度可能发生变化时,您真的只有几次标记时间,还是更像是一个连续体?我的意思是,“start1”标签对您有意义还是只是问题简化的一部分?在我的回答中,我假设您可以将您的数据视为在第二个 23 人 2 将其权重更改为 15...但我们可以对其进行调整... 另外,每个列表的初始值是什么意思(345)?我认为其余的值代表了那个人在每个时间点所承受的重量,但是看到allPeopleDf.columns=['intensity','id','timeid','time']. 的输出后我很困惑尝试解释你的真实数据是如何组织的我们可以调整代码来适应它。 继续钢琴示例: startx/endx 指的是一个键以一定强度演奏的开始和结束时间。 start1/end1 可以是每个键以强度 0 播放的时间,start2/end2 可以是每个键以强度 x 播放的时间等。 很抱歉与 3、4、5 混淆——这些是应用于最终结果的权重。例如,给定音符 A、B 和 C 的相同强度,也许一个人会更容易听到 C,所以我考虑了一些权重因素。我将把它留在示例中,这样我就不会因为删除它而造成更多混乱,但请随意忽略。 答案已编辑。我想现在应该没问题。 :) 【参考方案1】:

以钢琴键为例,假设您有 3 个键,有 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

您可以在其中记录任何键强度的每次变化。从这里你已经可以得到每个单独键的笛卡尔坐标为(time,intensity)

df[df.key=="A"].drop('key',1)

   time  intensity
0    10          5
3    15         15
4    20          7

然后,您可以轻松创建一个新列increment,它将指示该键在该时间点发生的强度变化(intensity 仅表示强度的新值)

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

然后,使用这个新列,您可以生成 (time, total_intensity) 对以用作笛卡尔坐标

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]: 避免在给定状态的 startend 时间相等时将数据加载到数据帧(似乎没有信息,并且不会出现在无论如何你的情节)。但如果您仍然想要这些条目,请将其取出。

然后,您加载数据:

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 替换 'key')

【讨论】:

我想这就是我要找的东西!我会检查一下,发布我的蛮力解决方案进行比较,并奖励赏金。谢谢。 我在上面添加了您的答案的实现。我在执行转换函数时遇到错误:...不支持的操作数类型用于 -: 'str' 和 'int'。我是 python 新手,仍在消化 df["increment"] 行。关于这个错误的来源有什么想法吗? 我在最后一行代码中添加了一个更正。我使用了无意义的列名,如“A”、“C”等进行代码测试,然后我忘记将“C”更改为“增量”。现在它被纠正了。但是,这不会导致您的错误。我评论了你的问题,试图澄清它! :) 这很棒。谢谢。【参考方案2】:

似乎是 .sum() 的用途:

In [10]:

allPeopleDf.sum()
Out[10]:
aStart     0
aEnd      35
bStart    35
bEnd      50
cStart    50
cEnd      90
dtype: int32

【讨论】:

我喜欢使用 .sum(),谢谢。在我的示例中,要重现黑线,我认为我需要每个时间步长的强度值总和:0、5、15、20、25、40。因此,在我的体重示例中,黑色图可能由以下笛卡尔点组成:(0,0 lbs)(5,0 lbs)(5,5 lbs)(15,5 lbs)(15,10 lbs)( 20,10 磅)(20,15 磅)(25,15 磅)(25,20 磅)(40,20 磅)。我将编辑问题以更好地反映这一点。

以上是关于在 python pandas 数据框中添加时间序列强度的廉价方法的主要内容,如果未能解决你的问题,请参考以下文章

如何在python的pandas数据框中输入单个值

如果 ID 存在于其他数据框中,则 Python Pandas 数据框在新列中添加“1”

Pandas:如何在数据透视表数据框中仅添加最新日期

在 Pandas 数据框中追加或添加行

Python - Pandas - 导入 Excel 文件,遍历每一行,添加新值,并添加到数据框

将 JSON 时间戳字符串转换为 pandas 数据框中的 python 日期