2016-03-22 4 views
0

У меня есть набор данных, показывающий, в каких городах каждый автомобиль был (как показано ниже в 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

Вот два варианта. Первый способ состоит в том, чтобы группировать по Vehicle_ID и для каждой группы создавать все комбинации двух городов. Соберите полученные пары городов и Vehicle_ID в наборе кортежей (так как мы не заботимся о повторяющихся пар городах), а затем используйте набор для создания нового DataFrame.Тогда groupby городские пары и подсчет различных Vehicle_ID сек:

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 генерирует прямой продукт cit у пар, так нам нужно удалить строки, где 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 

И теперь мы можем еще раз GroupBy 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. Например, с 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 

Однако, если мы генерируем DataFrame с 10000 строк и 50 Vehicle_ID с и 16 City с, затем 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_iteration и using_merge (и другие решения).

+0

Мне нравится ваше предложение об использовании 'merge', к вашему моменту о« больших данных », возможно, это будет подход, который следует предпринять, если бы вы были в SQL (возможно, SparkSQL или Redshift). просто для вашей справки вам не нужно 'if c1> c2', так как комбинации будут возвращать только отдельные« наборы »элементов (см. мой ответ), если вы хотите все возможные« кортежи », вы можете использовать' itertools.permuations ' – maxymoo

+0

Спасибо вам за советы. Оба слияния и itertool работают хорошо. Мой df1 довольно большой, так как вы предсказали, слияние работает быстрее. Надеюсь, хотя есть способ сделать условное слияние (или объединение), как вы могли бы в SQL, поэтому я могу сказать что-то вроде «где City_x

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 
Смежные вопросы