您好我正在尝试找到迭代问题的矢量化(或更有效)解决方案,其中我发现的唯一解决方案需要逐行迭代具有多个循环的DataFrame。实际的数据文件是巨大的,所以我目前的解决方案实际上是不可行的。如果你想看一下,我在最后包括了行分析器输出。真正的问题非常复杂,所以我将尝试用一个简单的例子来解释这个问题(花了我一点时间来简化它:)):
假设我们有一个机场有两个并排的着陆带。每架飞机降落(到达时间),在其中一个着陆带上停放一段时间,然后起飞(起飞时间)。所有内容都存储在Pandas DataFrame中,按照到达时间排序,如下所示(请参阅 EDIT2 以获取更大的数据集进行测试):
PLANE STRIP ARRIVAL DEPARTURE
0 1 85.00 86.00
1 1 87.87 92.76
2 2 88.34 89.72
3 1 88.92 90.88
4 2 90.03 92.77
5 2 90.27 91.95
6 2 92.42 93.58
7 2 94.42 95.58
寻找两种情况的解决方案:
1。构建一个事件列表,其中一次在一个条带上存在多个平面。不要包含事件的子集(例如,如果存在有效的[3,4,5]情况,则不显示[3,4])。该列表应存储实际DataFrame行的索引。有关此案例的解决方案,请参阅函数findSingleEvents()(运行大约5毫秒)。
2。建立一个事件列表,每次在每个条带上至少有一个平面。不计算事件的子集,仅记录具有最大平面数的事件。 (例如,如果有[3,4,5]个案,则不显示[3,4])。不要计算单个条带上完全发生的事件。该列表应存储实际DataFrame行的索引。有关此案例的解决方案,请参阅函数findMultiEvents()(运行大约15 ms)。
工作代码:
import numpy as np
import pandas as pd
import itertools
from __future__ import division
data = [{'PLANE':0, 'STRIP':1, 'ARRIVAL':85.00, 'DEPARTURE':86.00},
{'PLANE':1, 'STRIP':1, 'ARRIVAL':87.87, 'DEPARTURE':92.76},
{'PLANE':2, 'STRIP':2, 'ARRIVAL':88.34, 'DEPARTURE':89.72},
{'PLANE':3, 'STRIP':1, 'ARRIVAL':88.92, 'DEPARTURE':90.88},
{'PLANE':4, 'STRIP':2, 'ARRIVAL':90.03, 'DEPARTURE':92.77},
{'PLANE':5, 'STRIP':2, 'ARRIVAL':90.27, 'DEPARTURE':91.95},
{'PLANE':6, 'STRIP':2, 'ARRIVAL':92.42, 'DEPARTURE':93.58},
{'PLANE':7, 'STRIP':2, 'ARRIVAL':94.42, 'DEPARTURE':95.58}]
df = pd.DataFrame(data, columns = ['PLANE','STRIP','ARRIVAL','DEPARTURE'])
def findSingleEvents(df):
events = []
for row in df.itertuples():
#Create temporary dataframe for each main iteration
dfTemp = df[(row.DEPARTURE>df.ARRIVAL) & (row.ARRIVAL<df.DEPARTURE)]
if len(dfTemp)>1:
#convert index values to integers from long
current_event = [int(v) for v in dfTemp.index.tolist()]
#loop backwards to remove elements that do not comply
for i in reversed(current_event):
if (dfTemp.loc[i].ARRIVAL > dfTemp.DEPARTURE).any():
current_event.remove(i)
events.append(current_event)
#remove duplicate events
events = map(list, set(map(tuple, events)))
return events
def findMultiEvents(df):
events = []
for row in df.itertuples():
#Create temporary dataframe for each main iteration
dfTemp = df[(row.DEPARTURE>df.ARRIVAL) & (row.ARRIVAL<df.DEPARTURE)]
if len(dfTemp)>1:
#convert index values to integers from long
current_event = [int(v) for v in dfTemp.index.tolist()]
#loop backwards to remove elements that do not comply
for i in reversed(current_event):
if (dfTemp.loc[i].ARRIVAL > dfTemp.DEPARTURE).any():
current_event.remove(i)
#remove elements only on 1 strip
if len(df.iloc[current_event].STRIP.unique()) > 1:
events.append(current_event)
#remove duplicate events
events = map(list, set(map(tuple, events)))
return events
print findSingleEvents(df[df.STRIP==1])
print findSingleEvents(df[df.STRIP==2])
print findMultiEvents(df)
验证输出:
[[1, 3]]
[[4, 5], [4, 6]]
[[1, 3, 4, 5], [1, 4, 6], [1, 2, 3]]
显然,这些既不是有效也不是优雅的解决方案。使用我拥有的巨大DataFrame,运行它可能需要数小时。我想了一个矢量化的方法已经有一段时间了,但是没有什么可靠的。任何指针/帮助都会受到欢迎!我也对基于Numpy / Cython / Numba的方法开放。
谢谢!
PS:如果您想知道我将如何处理这些列表:我将为每个EVENT
分配一个EVENT
号码,并构建一个合并数据的单独数据库以上,EVENT
数字作为单独的列,用于其他内容。对于案例1,它看起来像这样:
EVENT PLANE STRIP ARRIVAL DEPARTURE
0 4 2 90.03 92.77
0 5 2 90.27 91.95
1 5 2 90.27 91.95
1 6 2 92.42 95.58
编辑:修改了代码和测试数据集。
EDIT2 :使用以下代码生成1000行(或更多)长DataFrame用于测试目的。 (根据@ImportanceOfBeingErnest的推荐)
import random
import pandas as pd
import numpy as np
data = []
for i in range(1000):
arrival = random.uniform(0,1000)
departure = arrival + random.uniform(2.0, 10.0)
data.append({'PLANE':i, 'STRIP':random.randint(1, 2),'ARRIVAL':arrival,'DEPARTURE':departure})
df = pd.DataFrame(data, columns = ['PLANE','STRIP','ARRIVAL','DEPARTURE'])
df = df.sort_values(by=['ARRIVAL'])
df = df.reset_index(drop=True)
df.PLANE = df.index
EDIT3:
已接受答案的修改版本。接受的答案无法删除事件子集。修改后的版本满足规则“(例如,如果有[3,4,5]个案例,则不显示[3,4])
def maximal_subsets_modified(sets):
sets.sort()
maximal_sets = []
s0 = frozenset()
for s in sets:
if not (s > s0) and len(s0) > 1:
not_in_list = True
for x in maximal_sets:
if set(x).issubset(set(s0)):
maximal_sets.remove(x)
if set(s0).issubset(set(x)):
not_in_list = False
if not_in_list:
maximal_sets.append(list(s0))
s0 = s
if len(s0) > 1:
not_in_list = True
for x in maximal_sets:
if set(x).issubset(set(s0)):
maximal_sets.remove(x)
if set(s0).issubset(set(x)):
not_in_list = False
if not_in_list:
maximal_sets.append(list(s0))
return maximal_sets
def maximal_subsets_2_modified(sets, d):
sets.sort()
maximal_sets = []
s0 = frozenset()
for s in sets:
if not (s > s0) and len(s0) > 1 and d.loc[list(s0), 'STRIP'].nunique() == 2:
not_in_list = True
for x in maximal_sets:
if set(x).issubset(set(s0)):
maximal_sets.remove(x)
if set(s0).issubset(set(x)):
not_in_list = False
if not_in_list:
maximal_sets.append(list(s0))
s0 = s
if len(s0) > 1 and d.loc[list(s), 'STRIP'].nunique() == 2:
not_in_list = True
for x in maximal_sets:
if set(x).issubset(set(s0)):
maximal_sets.remove(x)
if set(s0).issubset(set(x)):
not_in_list = False
if not_in_list:
maximal_sets.append(list(s0))
return maximal_sets
# single
def hal_3_modified(d):
sets = np.apply_along_axis(
lambda x: frozenset(d.PLANE.values[(d.PLANE.values <= x[0]) & (d.DEPARTURE.values > x[2])]),
1, d.values
)
return maximal_subsets_modified(sets)
# multi
def hal_5_modified(d):
sets = np.apply_along_axis(
lambda x: frozenset(d.PLANE.values[(d.PLANE.values <= x[0]) & (d.DEPARTURE.values > x[2])]),
1, d.values
)
return maximal_subsets_2_modified(sets, d)
答案 0 :(得分:1)
我使用DataFrame.apply
而不是迭代重写了解决方案,并且尽可能使用numpy数组进行优化。我使用frozenset
因为它们是不可变的并且可以清理,因此Series.unique
可以正常工作。 Series.unique
类型的元素set
失败。
此外,我发现d.loc[list(x), 'STRIP'].nunique()
略快于d.loc[list(x)].STRIP.nunique()
。我不确定原因,但我在下面的解决方案中使用了更快的陈述。
对于每一行,创建一组低于(或等于)当前索引的索引的索引,其索引的离开大于当前到达。这会产生一组集合。
返回不是其他集子集的唯一集合(对于第二个算法,另外过滤集合引用STRIP
的每个集合)
通过下降到numpy层并使用np.apply_along_axis
而不是使用df.apply进行了1次小改进。这个有可能
由于PLANE
始终等于数据框索引,我们可以使用df.values
我发现了列表理解的一个重大改进,它返回了最大子集
[list(x) for x in sets if ~np.any(sets > x)]
以上是O(n ^ 2)次序操作。在小型数据集上,这非常快。然而,在更大的数据集中,这种说法成了瓶颈。要优化此操作,首先对sets
进行排序,然后再次遍历元素以查找最大子集。一旦排序,检查elem [n]不是elem [n + 1]的子集就足以确定elem [n]是否是最大子集。排序过程将两个元素与<
操作
虽然我的原始实现与OP的尝试相比显着提高了性能,但算法是指数排序的,如下图所示。
我只提供findMultiEvents
,hal_2
&amp;的时间安排。 hal_5
。 findSinglEvents
,hal_1
&amp;的相对效果hal_3
同样具有可比性。
滚动下方以查看基准测试代码。
请注意,我停止了基准测试findMumtiEvents
&amp; hal_2
之前很明显,它们的效率低于指数因子
def maximal_subsets(sets):
sets.sort()
maximal_sets = []
s0 = frozenset()
for s in sets[::-1]:
if s0 > s or len(s) < 2:
continue
maximal_sets.append(list(s))
s0 = s
return maximal_sets
def maximal_subsets_2(sets, d):
sets.sort()
maximal_sets = []
s0 = frozenset()
for s in sets[::-1]:
if s0 > s or len(s) < 2 or d.loc[list(s), 'STRIP'].nunique() < 2:
continue
maximal_sets.append(list(s))
s0 = s
return maximal_sets
# single
def hal_3(d):
sets = np.apply_along_axis(
lambda x: frozenset(d.PLANE.values[(d.PLANE.values <= x[0]) & (d.DEPARTURE.values > x[2])]),
1, d.values
)
return maximal_subsets(sets)
# multi
def hal_5(d):
sets = np.apply_along_axis(
lambda x: frozenset(d.PLANE.values[(d.PLANE.values <= x[0]) & (d.DEPARTURE.values > x[2])]),
1, d.values
)
return maximal_subsets_2(sets, d)
# findSingleEvents
def hal_1(d):
sets = d.apply(
lambda x: frozenset(
d.index.values[(d.index.values <= x.name) & (d.DEPARTURE.values > x.ARRIVAL)]
),
axis=1
).unique()
return [list(x) for x in sets if ~np.any(sets > x) and len(x) > 1]
# findMultiEvents
def hal_2(d):
sets = d.apply(
lambda x: frozenset(
d.index.values[(d.index.values <= x.name) & (d.DEPARTURE.values > x.ARRIVAL)]
),
axis=1
).unique()
return [list(x) for x in sets
if ~np.any(sets > x) and
len(x) > 1 and
d.loc[list(x), 'STRIP'].nunique() == 2]
输出与OP的实现相同。
hal_1(df[df.STRIP==1])
[[1, 3]]
hal_1(df[df.STRIP==2])
[[4, 5], [4, 6]]
hal_2(df)
[[1, 2, 3], [1, 3, 4, 5], [1, 4, 6]]
hal_3(df[df.STRIP==1])
[[1, 3]]
hal_3(df[df.STRIP==2])
[[4, 5], [4, 6]]
hal_5(df)
[[1, 2, 3], [1, 3, 4, 5], [1, 4, 6]]
os: windows 10
python: 3.6 (Anaconda)
pandas: 0.22.0
numpy: 1.14.3
import random
def mk_random_df(n):
data = []
for i in range(n):
arrival = random.uniform(0,1000)
departure = arrival + random.uniform(2.0, 10.0)
data.append({'PLANE':i, 'STRIP':random.randint(1, 2),'ARRIVAL':arrival,'DEPARTURE':departure})
df = pd.DataFrame(data, columns = ['PLANE','STRIP','ARRIVAL','DEPARTURE'])
df = df.sort_values(by=['ARRIVAL'])
df = df.reset_index(drop=True)
df.PLANE = df.index
return df
dfs = {i: mk_random_df(100*(2**i)) for i in range(0, 10)}
times, times_2, times_5 = [], [], []
for i, v in dfs.items():
if i < 5:
t = %timeit -o -n 3 -r 3 findMultiEvents(v)
times.append({'size(pow. of 2)': i, 'timings': t})
for i, v in dfs.items():
t = %timeit -o -n 3 -r 3 hal_5(v)
times_5.append({'size(pow. of 2)': i, 'timings': t})
for i, v in dfs.items():
if i < 9:
t = %timeit -o -n 3 -r 3 hal_2(v)
times_2.append({'size(pow. of 2)': i, 'timings': t})