如何用Python Pandas计算时点值?
我们有一个表格(pandas.DataFrame
对象),里面显示了一个集群的CPU数量和内存使用情况。表格的每一行记录了某个工作的开始和结束时间(pandas.datetime
对象),以及在这个时间段内的CPU和内存使用情况(因为工作是独立的,所以时间段可能会重叠):
In [505]: data.head()
Out[505]:
created_at deleted_at vcpus memory_mb
0 2013-11-08 18:26:34 2013-11-08 18:27:52 1 512
1 2013-11-08 18:27:53 2013-11-08 18:31:07 1 512
2 2013-11-08 18:30:24 2013-11-08 18:46:35 1 2000
3 2013-11-08 18:30:24 2013-11-08 18:46:31 1 2000
4 2013-11-08 18:30:25 2013-11-08 18:46:36 1 2000
我们想要创建一个图表,显示去年整年的总CPU和内存使用情况。
这就意味着我们需要知道,在某个时间点t
,所有记录中created_at <= t <= deleted_at
的vcpus
列的总和是多少。
我们可以用什么代码来完成这个任务呢?最快的解决方案是什么?(因为我们需要处理几百万条记录,所以速度非常重要。)
2 个回答
0
一种解决方案是使用Python的列表推导式,结合NumPy风格的整体数组索引:
# load data
data = pandas.read_csv('accounting.csv', sep='\t', parse_dates=['created_at', 'deleted_at'], na_values=['NULL'])
# create array of sampling times, using 1-minute resolution
mindate = data.created_at.min()
maxdate = data.deleted_at.max()
tm = pandas.date_range(mindate, maxdate, freq='Min')
# for each point in time `t`, select all records such that
# `created_at <= t <= deleted_at` and then take the sum of CPUs.
num_vcpus = pandas.Series(data['vcpus'][(data['created_at'] >= t) & (data['deleted_at'] <= t)].sum() for t in tm)
这样做是可行的,但有个缺点就是如果我们想计算,比如说内存使用情况,就得再次运行同样的生成器表达式。此外,我们还会创建很多临时的NumPy布尔数组来保存日期/时间比较的结果。
0
另一种解决方案是使用纯Python的循环:
- 首先,创建一个采样时间的数组,
- 接着,为我们想要计算的值创建NumPy数组:
- 每一组独立值用一个数组(例如,CPU数量、内存)
- 每个采样时间对应一个数组的条目
- 对于每一行的
DataFrame
,在开始和结束时间之间的整个数组条目中添加相应的值
最后一点,如果时间是以某个起点(比如UNIX的“纪元”)为基准的秒数来表示,那么这个过程可以简化为比较简单的算术运算。
代码示例:
# load data file
data = pandas.read_csv('accounting.csv', sep='\t', parse_dates=['created_at', 'deleted_at'], na_values=['NULL'])
mindate = data.created_at.min()
maxdate = data.deleted_at.max()
# fill missing dates
data['deleted_at'] = pandas.to_datetime(data['deleted_at'].fillna(maxdate))
data['created_at'] = pandas.to_datetime(data['created_at'].fillna(mindate))
# inefficient conversion function
def epoch(date):
return int(date.strftime("%s"))
# sample times, equally spaced at 1-minute interval
tm = pandas.date_range(mindate, maxdate, freq='Min')
ts=pandas.DataFrame(tm)
# convert datetime to UNIX epoch
epochs=ts[0].astype(pandas.np.int64)//10**9
minepoch=min(epochs)
step=60
vcpus = pandas.np.zeros(len(tm))
mem = pandas.np.zeros(len(tm))
for idx, row in data.iterrows():
t0 = epoch(row[0])
t1 = epoch(row[1])
ix0 = (t0 - minepoch) / step
ix1 = (t1 - minepoch) / step
for index in range(ix0, ix1):
vcpus[index] += row[5] # vcpus @ index 5
mem[index] += row[6] # memory_mb @ index 6
这样可以同时计算很多列,但循环还是在Python代码中,并且Pandas的.iterrows()
函数需要为每一行创建一个pandas.Series
对象——所以当行数很多时,这样做可能会效率不高。