按组合计数
Count by combination
我有一个数据集显示每辆车去过哪些城市(如下面的 df1 所示)。
我正在尝试创建一个基于 df1 的双城市组合列表,然后针对每个双城市组合计算有多少车辆去过该特定的双城市组合(如下面的 df2)。
我四处寻找但找不到解决方案。有人对此有解决方案吗?
(任何帮助将不胜感激)
df1= pd.DataFrame([
[1,'A'],[1,'B'],[1,'C'],
[2,'A'],[2,'C'],[2,'C'],[2,'A'],
[3,'C'],[3,'B'],[3,'C'],[3,'B']],columns=['Vehicle_ID','City'])
df2= pd.DataFrame([['A,B',1],['B,C',2],['A,C',2]],
columns=['City_Combination','Vehicle_Count'])
注:
(1) 访问城市的顺序无关紧要。例如。在('A,B')组合下,访问过(A -> B)或(B -> A)或(A -> C -> B)的车辆都将被统计。
(2) 访问城市的频率无关紧要。例如。在 ('A,B') 组合下,访问过 (A -> B -> A -> A) 的车辆仍算作 1 辆车。
首先让我们旋转 table,使城市成为列,每辆车一行:
In [50]: df1['n'] = 1
In [51]: df = df1.pivot_table(index='Vehicle_ID', columns = 'City', values = 'n', aggfunc=sum)
df
Out[51]:
City A B C
Vehicle_ID
1 1 1 1
2 2 NaN 2
3 NaN 2 2
现在我们可以使用 itertools.combinations
获得组合(注意我们必须强制 list
一次查看所有值,因为默认情况下 itertools returns 是一个迭代器):
from itertools import combinations
city_combos = list(combinations(df1.City.unique(), 2))
city_combos
Out[19]: [('A', 'B'), ('A', 'C'), ('B', 'C')]
最后我们可以遍历组合并计算计数:
In [87]: pd.Series({c:df[list(c)].notnull().all(axis=1).sum() for c in city_combos})
Out[87]:
A B 1
C 2
B C 2
dtype: int64
这里有两个选项。第一种方法是按 Vehicle_ID
分组,并为每个组生成两个城市的所有组合。将生成的城市对和 Vehicle_ID
收集在一组元组中(因为我们不关心重复的城市对),然后使用该集合生成一个新的 DataFrame。然后 groupby
城市配对并计算不同的 Vehicle_ID
s:
df1 = df1.drop_duplicates()
data = set()
for vid, grp in df1.groupby(['Vehicle_ID']):
for c1, c2 in IT.combinations(grp['City'], 2):
if c1 > c2:
c1, c2 = c2, c1
data.add((c1, c2, vid))
df = pd.DataFrame(list(data), columns=['City_x', 'City_y', 'Vehicle_Count'])
# City_x City_y Vehicle_Count
# 0 B C 3
# 1 A C 1
# 2 B C 1
# 3 A C 2
# 4 A B 1
result = df.groupby(['City_x', 'City_y']).count()
产量
Vehicle_Count
City_x City_y
A B 1
C 2
B C 2
另一种方法是将 df1
与其自身合并:
In [244]: df1 = df1.drop_duplicates()
In [246]: df3 = pd.merge(df1, df1, on='Vehicle_ID', how='left'); df3
Out[246]:
Vehicle_ID City_x City_y
0 1 A A
1 1 A B
2 1 A C
3 1 B A
4 1 B B
5 1 B C
6 1 C A
7 1 C B
8 1 C C
9 2 A A
10 2 A C
11 2 C A
12 2 C C
13 3 C C
14 3 C B
15 3 B C
16 3 B B
不幸的是,pd.merge
生成了城市对的直积,所以
我们需要删除 City_x >= City_y
:
的行
In [247]: mask = df3['City_x'] < df3['City_y']
In [248]: df3 = df3.loc[mask]; df3
Out[249]:
Vehicle_ID City_x City_y
1 1 A B
2 1 A C
5 1 B C
10 2 A C
15 3 B C
现在我们可以再次分组 City_x
,City_y
并计算结果:
In [251]: result = df3.groupby(['City_x', 'City_y']).count(); result
Out[251]:
Vehicle_ID
City_x City_y
A B 1
C 2
B C 2
import numpy as np
import pandas as pd
import itertools as IT
def using_iteration(df1):
df1 = df1.drop_duplicates()
data = set()
for vid, grp in df1.groupby(['Vehicle_ID']):
for c1, c2 in IT.combinations(grp['City'], 2):
if c1 > c2:
c1, c2 = c2, c1
data.add((c1, c2, vid))
df = pd.DataFrame(list(data), columns=['City_x', 'City_y', 'Vehicle_Count'])
result = df.groupby(['City_x', 'City_y']).count()
return result
def using_merge(df1):
df1 = df1.drop_duplicates()
df3 = pd.merge(df1, df1, on='Vehicle_ID', how='left')
mask = df3['City_x'] < df3['City_y']
df3 = df3.loc[mask]
result = df3.groupby(['City_x', 'City_y']).count()
result = result.rename(columns={'Vehicle_ID':'Vehicle_Count'})
return result
def generate_df(nrows, nids, strlen):
cities = (np.random.choice(list('ABCD'), nrows*strlen)
.view('|S{}'.format(strlen)))
ids = np.random.randint(nids, size=(nrows,))
return pd.DataFrame({'Vehicle_ID':ids, 'City':cities})
df1 = pd.DataFrame([
[1, 'A'], [1, 'B'], [1, 'C'],
[2, 'A'], [2, 'C'], [2, 'C'], [2, 'A'],
[3, 'C'], [3, 'B'], [3, 'C'], [3, 'B']], columns=['Vehicle_ID', 'City'])
df = generate_df(10000, 50, 2)
assert using_merge(df).equals(using_iteration(df))
如果df1
很小,using_iteration
可能比using_merge
快。例如,
使用原始 post、
中的 df1
In [261]: %timeit using_iteration(df1)
100 loops, best of 3: 3.45 ms per loop
In [262]: %timeit using_merge(df1)
100 loops, best of 3: 4.39 ms per loop
但是,如果我们生成一个包含 10000 行和 50 Vehicle_ID
s 和 16 City
s 的 DataFrame,
那么 using_merge
可能比 using_iteration
:
快
df = generate_df(10000, 50, 2)
In [241]: %timeit using_merge(df)
100 loops, best of 3: 7.73 ms per loop
In [242]: %timeit using_iteration(df)
100 loops, best of 3: 16.3 ms per loop
一般来说,for-loops
需要的迭代次数越多
using_iteration
——即更多的Vehicle_ID
和可能的城市对——
更有可能基于 NumPy 或 Pandas 的方法(例如 pd.merge
)会更快。
但是请注意,pd.merge
生成的 DataFrame 比我们最终需要的要大。所以 using_merge
可能需要比 using_iteration
更多的内存。所以在某些时候,对于足够大的 df1
s,using_merge
可能需要交换 space,这会使 using_merge
比 using_iteration
慢。
所以最好在您的实际数据上测试 using_iteration
和 using_merge
(以及其他解决方案),看看哪个最快。
我有一个数据集显示每辆车去过哪些城市(如下面的 df1 所示)。
我正在尝试创建一个基于 df1 的双城市组合列表,然后针对每个双城市组合计算有多少车辆去过该特定的双城市组合(如下面的 df2)。
我四处寻找但找不到解决方案。有人对此有解决方案吗? (任何帮助将不胜感激)
df1= pd.DataFrame([
[1,'A'],[1,'B'],[1,'C'],
[2,'A'],[2,'C'],[2,'C'],[2,'A'],
[3,'C'],[3,'B'],[3,'C'],[3,'B']],columns=['Vehicle_ID','City'])
df2= pd.DataFrame([['A,B',1],['B,C',2],['A,C',2]],
columns=['City_Combination','Vehicle_Count'])
注:
(1) 访问城市的顺序无关紧要。例如。在('A,B')组合下,访问过(A -> B)或(B -> A)或(A -> C -> B)的车辆都将被统计。
(2) 访问城市的频率无关紧要。例如。在 ('A,B') 组合下,访问过 (A -> B -> A -> A) 的车辆仍算作 1 辆车。
首先让我们旋转 table,使城市成为列,每辆车一行:
In [50]: df1['n'] = 1
In [51]: df = df1.pivot_table(index='Vehicle_ID', columns = 'City', values = 'n', aggfunc=sum)
df
Out[51]:
City A B C
Vehicle_ID
1 1 1 1
2 2 NaN 2
3 NaN 2 2
现在我们可以使用 itertools.combinations
获得组合(注意我们必须强制 list
一次查看所有值,因为默认情况下 itertools returns 是一个迭代器):
from itertools import combinations
city_combos = list(combinations(df1.City.unique(), 2))
city_combos
Out[19]: [('A', 'B'), ('A', 'C'), ('B', 'C')]
最后我们可以遍历组合并计算计数:
In [87]: pd.Series({c:df[list(c)].notnull().all(axis=1).sum() for c in city_combos})
Out[87]:
A B 1
C 2
B C 2
dtype: int64
这里有两个选项。第一种方法是按 Vehicle_ID
分组,并为每个组生成两个城市的所有组合。将生成的城市对和 Vehicle_ID
收集在一组元组中(因为我们不关心重复的城市对),然后使用该集合生成一个新的 DataFrame。然后 groupby
城市配对并计算不同的 Vehicle_ID
s:
df1 = df1.drop_duplicates()
data = set()
for vid, grp in df1.groupby(['Vehicle_ID']):
for c1, c2 in IT.combinations(grp['City'], 2):
if c1 > c2:
c1, c2 = c2, c1
data.add((c1, c2, vid))
df = pd.DataFrame(list(data), columns=['City_x', 'City_y', 'Vehicle_Count'])
# City_x City_y Vehicle_Count
# 0 B C 3
# 1 A C 1
# 2 B C 1
# 3 A C 2
# 4 A B 1
result = df.groupby(['City_x', 'City_y']).count()
产量
Vehicle_Count
City_x City_y
A B 1
C 2
B C 2
另一种方法是将 df1
与其自身合并:
In [244]: df1 = df1.drop_duplicates()
In [246]: df3 = pd.merge(df1, df1, on='Vehicle_ID', how='left'); df3
Out[246]:
Vehicle_ID City_x City_y
0 1 A A
1 1 A B
2 1 A C
3 1 B A
4 1 B B
5 1 B C
6 1 C A
7 1 C B
8 1 C C
9 2 A A
10 2 A C
11 2 C A
12 2 C C
13 3 C C
14 3 C B
15 3 B C
16 3 B B
不幸的是,pd.merge
生成了城市对的直积,所以
我们需要删除 City_x >= City_y
:
In [247]: mask = df3['City_x'] < df3['City_y']
In [248]: df3 = df3.loc[mask]; df3
Out[249]:
Vehicle_ID City_x City_y
1 1 A B
2 1 A C
5 1 B C
10 2 A C
15 3 B C
现在我们可以再次分组 City_x
,City_y
并计算结果:
In [251]: result = df3.groupby(['City_x', 'City_y']).count(); result
Out[251]:
Vehicle_ID
City_x City_y
A B 1
C 2
B C 2
import numpy as np
import pandas as pd
import itertools as IT
def using_iteration(df1):
df1 = df1.drop_duplicates()
data = set()
for vid, grp in df1.groupby(['Vehicle_ID']):
for c1, c2 in IT.combinations(grp['City'], 2):
if c1 > c2:
c1, c2 = c2, c1
data.add((c1, c2, vid))
df = pd.DataFrame(list(data), columns=['City_x', 'City_y', 'Vehicle_Count'])
result = df.groupby(['City_x', 'City_y']).count()
return result
def using_merge(df1):
df1 = df1.drop_duplicates()
df3 = pd.merge(df1, df1, on='Vehicle_ID', how='left')
mask = df3['City_x'] < df3['City_y']
df3 = df3.loc[mask]
result = df3.groupby(['City_x', 'City_y']).count()
result = result.rename(columns={'Vehicle_ID':'Vehicle_Count'})
return result
def generate_df(nrows, nids, strlen):
cities = (np.random.choice(list('ABCD'), nrows*strlen)
.view('|S{}'.format(strlen)))
ids = np.random.randint(nids, size=(nrows,))
return pd.DataFrame({'Vehicle_ID':ids, 'City':cities})
df1 = pd.DataFrame([
[1, 'A'], [1, 'B'], [1, 'C'],
[2, 'A'], [2, 'C'], [2, 'C'], [2, 'A'],
[3, 'C'], [3, 'B'], [3, 'C'], [3, 'B']], columns=['Vehicle_ID', 'City'])
df = generate_df(10000, 50, 2)
assert using_merge(df).equals(using_iteration(df))
如果df1
很小,using_iteration
可能比using_merge
快。例如,
使用原始 post、
df1
In [261]: %timeit using_iteration(df1)
100 loops, best of 3: 3.45 ms per loop
In [262]: %timeit using_merge(df1)
100 loops, best of 3: 4.39 ms per loop
但是,如果我们生成一个包含 10000 行和 50 Vehicle_ID
s 和 16 City
s 的 DataFrame,
那么 using_merge
可能比 using_iteration
:
df = generate_df(10000, 50, 2)
In [241]: %timeit using_merge(df)
100 loops, best of 3: 7.73 ms per loop
In [242]: %timeit using_iteration(df)
100 loops, best of 3: 16.3 ms per loop
一般来说,for-loops
需要的迭代次数越多
using_iteration
——即更多的Vehicle_ID
和可能的城市对——
更有可能基于 NumPy 或 Pandas 的方法(例如 pd.merge
)会更快。
但是请注意,pd.merge
生成的 DataFrame 比我们最终需要的要大。所以 using_merge
可能需要比 using_iteration
更多的内存。所以在某些时候,对于足够大的 df1
s,using_merge
可能需要交换 space,这会使 using_merge
比 using_iteration
慢。
所以最好在您的实际数据上测试 using_iteration
和 using_merge
(以及其他解决方案),看看哪个最快。