Как векторизовать работу панд, чтобы улучшить скорость? - PullRequest
1 голос
/ 24 апреля 2019

Это проблема близости SKU.У меня есть такой кадр данных.Каждый ctn_id имеет несколько кодов sku_.

enter image description here

dfr = pd.DataFrame(columns=['ctn_id','sku_code'])
dfr['ctn_id'] = np.random.randint(low=1,high=21,size=200)
dfr['sku_code'] = np.random.choice(['a','b','c','d'],size=200)
dfr.drop_duplicates(['ctn_id','sku_code'], inplace=True)

Я хочу заполнить следующий фрейм данных.

enter image description here

dfx = pd.DataFrame(columns=['sku_code','a','b','c','d'])
dfx['sku_code'] = ['a','b','c','d']
dfx = dfx.fillna(0)
dfx.set_index('sku_code',inplace=True)

с использованием приведенной ниже логики

for idx in dfr['ctn_id'].unique():
    x = list(dfr[dfr['ctn_id'] == idx]['sku_code'].unique())
    for skui in dfx.index:
        if skui in x:
            for skuj in x:
                dfx.loc[skui, skuj] = dfx.loc[skui, skuj] + 1

У меня есть 2,5M ctn_ids и 400 sk_codes, что составляет в общей сложности миллиард операций присваивания.Есть ли лучший способ сделать это, используя панд или любой другой пакет?

Ответы [ 3 ]

2 голосов
/ 25 апреля 2019

Для ctn_id, имеющего integers,, мы можем использовать метод массив-присвоение , чтобы получить все отображения в сетке 2D, а затем использовать матричное умножение, чтобы получить binned- суммирования , аналогичные показанным в @scomes's post -

Ie = dfr.ctn_id.values
J = dfr.sku_code.values

I = pd.factorize(Ie,sort=False)[0]
col2IDs,col2L = pd.factorize(J,sort=True) #use sort=False if order is irrelevant
a = np.zeros((I.max()+1,col2IDs.max()+1),dtype=int)
a[I,col2IDs] = 1
df_out = pd.DataFrame(a.T.dot(a), columns=col2L, index=col2L)

Альтернатива # 1

Для лучшей производительности мы можем использовать float значения для умножения матриц. Для этого используйте float dtype, чтобы получить a. Следовательно, настройка a, вот так -

a = np.zeros((I.max()+1,col2IDs.max()+1),dtype=float)

Альтернатива # 2

Или используйте логический массив для хранения 1s, а затем преобразуйте dtype:

a = np.zeros((I.max()+1,col2IDs.max()+1),dtype=bool)
a[I,col2IDs] = 1
a = a.astype(float)
2 голосов
/ 25 апреля 2019

Обновлено для обработки дубликатов из случайного ввода

В этом ответе предполагается, что нет повторяющихся строк (строк с одинаковыми ctn_id и sku_code).Вы можете легко расширить этот ответ для этого варианта использования.

Да, вы можете повернуть фрейм данных так, чтобы ctn_ids были строками, а sku_codes - столбцами.Чтобы сделать это, вы можете добавить фиктивный столбец со всеми 1, а затем использовать

dfr['Dummy'] = 1
piv = dfr.drop_duplicates().pivot('ctn_id', 'sku_code', 'Dummy').fillna(0.0)

Теперь у вас есть по существу разреженная матрица с 1, где есть отношение ctn_id / sku_code, и 0 в противном случае.Отсюда вы можете просто использовать матричную алгебру.

mat = piv.values
counts = mat.T.dot(mat)

Переменная counts содержит то, что вы ищете (она будет симметричной, а значения будут числом раз, когда sku_codes будут видны вместе в ctn_id, что, как я полагаю, вы ищетеза.

1 голос
/ 25 апреля 2019

Хорошо, я сделаю это.

Не уверен, что это будет достаточно быстро , но я бы сказал, что это уже намного быстрее, чем ваша цепочка для петель.

Используется хакерский способ выполнения «векторизации» разности установок.

s = df.groupby(['sku_code']).ctn_id.agg(set)
pd.DataFrame(map(lambda s: list(map(len,s)), np.array(s) & np.array(s).reshape([-1,1])))

    0   1   2   3
0   18  17  18  16
1   17  19  19  17
2   18  19  20  17
3   16  17  17  17

С предоставленным вами образцом есть ~Увеличение производительности в 100 раз.

# your method
79.4 ms ± 3.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# my try
668 µs ± 30.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
...