【问题标题】:Do I need to iterate through every row of data to calculate time per column category?我是否需要遍历每一行数据来计算每个列类别的时间?
【发布时间】:2016-11-11 17:46:34
【问题描述】:

我在 python 中有如下表所示的数据列表。

基本上,它是通过观察我们的机器人在我们的迷宫/竞技场中所做的事情而产生的。我们有事件的时间戳,目前时间戳是事件驱动的,而不是周期性的。

我需要以有效的方式找到在每个竞技场中花费的时间。

TimeStamp   Arena
101         Arena A
109         Arena A
112         Arena B
113         Arena A
118         Arena A
120         Arena D
125         Arena D
129         Arena D
138         Arena B
139         Arena B
148         Arena C
149         Arena C
150         Arena B
151         Arena B
159         Arena D
169         Arena D
171         Arena D
172         Arena D
175         Arena B
177         Arena B
180         Arena B
181         Arena A
182         Arena A
189         Arena E
200         Arena E
204         Arena E
208         Arena A
209         Arena A

基本上,我需要在下面得到这个。每个竞技场花费的总时间。

 Arena  TimeStamp
Arena D         32
Arena B         23
Arena E         22
Arena A         16
Arena C         10

我现在编写了一个简单的脚本来执行此操作。

import pandas as pd

data = pd.read_csv('arenas_visited.csv')


l = len(data[[1]])
first_arena = data.loc[0, 'Arena']
start_time = data.loc[0, 'TimeStamp']

summary = []

for i in range(0,l):

try:
    next_arena = data.loc[i+1, 'Arena']
except:
    break     

first_arena = data.loc[i, 'Arena']   

if first_arena != next_arena:

    change_time = data.loc[i, 'TimeStamp']
    time_spent = change_time - start_time
    arena = str(data.loc[i, 'Arena'])
    summary.append([arena, time_spent])
    start_time = change_time
    first_arena = data.loc[i+1, 'Arena']   

    if i == l-2:
        if data.loc[i, 'Arena'] != data.loc[i+1, 'Arena']:
            time_spent = 1
            arena = str(data.loc[i+1, 'Arena'])
            print (str(1) + " Spent in " + arena)
            summary.append([arena, time_spent])

else:
    pass

aggregated = pd.DataFrame(summary, columns = ['Arena', 'TimeStamp'])
time_per_arena = aggregated.groupby(['Arena']).sum().sort_values('TimeStamp',  ascending=False).reset_index()
print time_per_arena

基本上,虽然这工作得很好。但是,我最终将拥有数百万行此类数据,我需要找到一种更快的方法来执行此操作。

但是,除了遍历每一行之外,我没有看到任何其他方法?

是我没有考虑的事情吗?

【问题讨论】:

  • 如果你只需要按竞技场的总结,你为什么不直接计算呢?
  • 我需要获取时间戳的差异,将它们分组是行不通的,因为这样我就无法分辨哪个进入或离开了房间。

标签: python pandas dataframe data-processing


【解决方案1】:

创建时间增量向量,然后对其进行分组和求和:

df['delta'] = df.TimeStamp - df.TimeStamp.shift()

df.groupby('Arena').delta.sum()
Out[62]: 
Arena
Arena_A    21.0
Arena_B    23.0
Arena_C    10.0
Arena_D    32.0
Arena_E    22.0
Name: delta, dtype: float64

【讨论】:

  • 这看起来很有用。您是针对我的数据运行它还是这只是一个示例?因为答案不同。我发现了一些错误,但我会测试这个方法并让你知道这是否有效。谢谢布德!
  • 上面的数据的简单复制粘贴。你试图以矢量化的方式完成的数学就是我向你描述的。我没有阅读您的代码,因为您对您尝试完成的工作的解释已经足够了。
  • 缺少的 5 个可能对应于 208 和 209,这可能会在您的代码中引发异常并中断。当您手动计算时,您不会在代码中考虑该限制,它是 21。这是一个很好的说明,您不应该采用 for loop in rows 方法,因为它非常慢且容易出错。经验法则是,当您使用数据框时,始终要找到您尝试计算的内容背后的向量数学逻辑。 For 循环是最后的邪恶解决方案。
【解决方案2】:

Python 有很多其他语言不会自动内置的好东西。如果可以的话,没有理由自己对数据进行索引:

result = {}
old_arena = None
old_timestamp = 0
# I don't have a lot of experience with panda, so you may need to massage the 
# input to be able to do this
for line in data:
    timestamp, _, arena = line.split()
    if arena == old_arena:
        continue
    timestamp = int(timestamp)
    try:
        result[old_arena] += timestamp - old_timestamp
    except:
        result[old_arena] = timestamp - old_timestamp

    old_arena = arena
    old_timestamp = timestamp

# Process the last interval - if the last one was changed, then
# old_timestamp will equal timestamp and this is fine    
result[old_arena] += int(timestamp) - old_timestamp

这将以O(n) 时间和O(n+k) 空间复杂度一次性处理整个列表,其中 k 是竞技场的数量。

结果是一个包含(其中 None 表示初始时间偏移量)的 dict:

{'A': 27, 'C': 2, 'B': 26, 'E': 19, 'D': 34, None: 101}

就您的示例数据而言:值得注意的是,这会转换到 old_arena,这可能不是您想要的。


如果您希望它在过渡到下一个竞技场的位置,通过反转我们的遍历来提供一个小的编辑:

result = {}
old_arena = None
old_timestamp = 0
# I don't have a lot of experience with panda, so you may need to massage the 
# input to be able to do this
for line in reversed(data):
    timestamp, _, arena = line.split()
    if arena == old_arena:
        continue
    timestamp = int(timestamp)
    try:
        result[old_arena] += old_timestamp - timestamp
    except:
        result[old_arena] = old_timestamp - timestamp

    old_arena = arena
    old_timestamp = timestamp

# Process the last interval - if the last one was changed, then 
# old_timestamp will equal timestamp and this is fine    
result[old_arena] += old_timestamp - int(timestamp)

这给出了:

{'A': 21, 'C': 10, 'B': 23, 'E': 22, 'D': 32, None: -209}

【讨论】:

  • 非常有趣的解决方案。立即测试!
  • @StacyGarfield 我添加了反向解决方案:过渡到新竞技场的位置。
  • @StacyGarfield 我已经修复了一个阻止它添加最后一个间隔的错误
猜你喜欢
  • 2020-04-10
  • 1970-01-01
  • 2016-06-21
  • 2016-12-30
  • 2019-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-03
相关资源
最近更新 更多