按组合计算

时间:2016-03-22 00:01:42

标签: python pandas itertools

我有一个数据集,显示每辆车去过的城市(如下面的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车辆。

2 个答案:

答案 0 :(得分:1)

以下是两个选项。第一种方法是按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_xCity_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更快。例如, 使用原始帖子中的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可能需要交换空间,这可能使using_merge慢于using_iteration

因此,最好在实际数据上测试using_iterationusing_merge(及其他解决方案),以了解最快的数据。

答案 1 :(得分:0)

首先让我们转动表格,使城市成为列,每辆车只有一行:

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会返回一个迭代器):

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