Вот два варианта. Первый способ состоит в том, чтобы группировать по 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
(и другие решения).
Мне нравится ваше предложение об использовании 'merge', к вашему моменту о« больших данных », возможно, это будет подход, который следует предпринять, если бы вы были в SQL (возможно, SparkSQL или Redshift). просто для вашей справки вам не нужно 'if c1> c2', так как комбинации будут возвращать только отдельные« наборы »элементов (см. мой ответ), если вы хотите все возможные« кортежи », вы можете использовать' itertools.permuations ' – maxymoo
Спасибо вам за советы. Оба слияния и itertool работают хорошо. Мой df1 довольно большой, так как вы предсказали, слияние работает быстрее. Надеюсь, хотя есть способ сделать условное слияние (или объединение), как вы могли бы в SQL, поэтому я могу сказать что-то вроде «где City_x