Панды питона умножают фрейм данных на веса, которые варьируются в зависимости от категории в векторизованном виде

Моя проблема очень похожа на изложенную здесь

За исключением этого мой основной фрейм данных имеет столбец категории, как и мои веса:

df
Out[3]: 
Symbol         var_1      var_2     var_3     var_4    Category  
Index                                              
1903          0.000443  0.006928  0.000000  0.012375      A
1904         -0.000690 -0.007873  0.000171  0.014824      A
1905         -0.001354  0.001545  0.000007 -0.008195      C
1906         -0.001578  0.008796 -0.000164  0.015955      D
1907         -0.001578  0.008796 -0.000164  0.015955      A
1909         -0.001354  0.001545  0.000007 -0.008195      B


wgt_df
Out[4]: 
  Category   var_1_wgt var_2_wgt var_3_wgt var_4_wgt
0    A       0.182022   0.182022  0.131243  0.182022
1    B       0.534814   0.534814  0.534814  0.534814
2    C       0.131243   0.534814  0.131243  0.182022  
3    D       0.182022   0.151921  0.151921  0.131243

Я пытаюсь применить правильные веса для каждой категории, чтобы создать новый столбец df['new_var'], который является взвешенной суммой. В случае без категории, я могу преобразовать веса в массив Numpy и использовать .dot() метод, который кажется очень быстрым. Однако я не вижу, как это сделать с моей проблемой: если я использую groupby() на основном фрейме данных, df, я, безусловно, должен каким-то образом сделать то же самое с моим фреймом данных, wgt_df.

На самом деле, df содержит несколько миллионов строк, и мне нужно многократно повторять это вычисление, поэтому я стремлюсь найти векторизованное решение; Я мог бы иначе df.groupby('Category')создайте набор данных для фреймов, ключом которых является категория, например wgts_dict['A'] = wgts_df[wgts_df.Category == 'A']и применить мою точечную логику через lambda x, хотя я также не уверен, как это сделать, так как мне нужно было бы явно указать, какой элемент группы обрабатывался в данный момент, чтобы вытащить правильный фрагмент wgts_df,

2 ответа

Решение

Вы можете сделать групповой (выбрать по категории), а затем сделать dot() или вы можете сделать dot() а затем выберите по категории. Последний быстрее и проще в пандах. Обратите внимание, что данные, которые я использовал, соответствуют именам столбцов в данных и фреймах весов.

Код для dot() и затем выберите:

df['dot'] = df[df_wgt.columns].dot(df_wgt.T).lookup(df.index, df.Category)

Шаги выполнены...

  1. Выберите столбцы для использования с df[df_wgt.columns]

    При этом используются метки столбцов и порядок из массива данных. Это важно потому что dot() нужны данные в правильном порядке.

  2. Выполнение скалярного произведения против транспонированного массива данных с .dot(df_wgt.T)

    Транспонирование веса помещает их в правильную ориентацию для .dot(), Это делает расчет для всех весовых категорий для каждой строки данных. Это означает, что в этом случае мы делаем в четыре раза больше умножений, чем потребуется, но все же, вероятно, быстрее, чем группирование.

  3. Выберите нужный точечный продукт с помощью .lookup(df.index, df.Category)

    Используя lookup() мы можем собрать правильный результат для категории этой строки.

Код для выбора (групповой), а затем dot() :

def dot(group):
    category = group['Category'].iloc[0]
    weights = df_wgt.loc[category].values
    return pd.Series(
        np.dot(group[df_wgt.columns].values, weights), index=group.index)

df['dot'] = df.groupby(['Category']).apply(dot) \
    .reset_index().set_index('Index')[0]

Тестовый код:

import pandas as pd
from io import StringIO

df = pd.read_fwf(StringIO(u"""
    Index          var_1      var_2     var_3     var_4    Category
    1903          0.000443  0.006928  0.000000  0.012375      A
    1904         -0.000690 -0.007873  0.000171  0.014824      A
    1905         -0.001354  0.001545  0.000007 -0.008195      C
    1906         -0.001578  0.008796 -0.000164  0.015955      D
    1907         -0.001578  0.008796 -0.000164  0.015955      A
    1909         -0.001354  0.001545  0.000007 -0.008195      B"""),
                 header=1, skiprows=0).set_index(['Index'])

df_wgt = pd.read_fwf(StringIO(u"""
     Category     var_1      var_2     var_3     var_4
        A       0.182022   0.182022  0.131243  0.182022
        B       0.534814   0.534814  0.534814  0.534814
        C       0.131243   0.534814  0.131243  0.182022
        D       0.182022   0.151921  0.151921  0.131243"""),
                 header=1, skiprows=0).set_index(['Category'])

df['dot'] = df[df_wgt.columns].dot(df_wgt.T).lookup(df.index, df.Category)
print(df)

Результаты:

          var_1     var_2     var_3     var_4 Category       dot
Index                                                           
1903   0.000443  0.006928  0.000000  0.012375        A  0.003594
1904  -0.000690 -0.007873  0.000171  0.014824        A  0.001162
1905  -0.001354  0.001545  0.000007 -0.008195        C -0.000842
1906  -0.001578  0.008796 -0.000164  0.015955        D  0.003118
1907  -0.001578  0.008796 -0.000164  0.015955        A  0.004196
1909  -0.001354  0.001545  0.000007 -0.008195        B -0.004277

Настроить

print(df)
Out[655]: 
           var_1     var_2     var_3     var_4 Category
Symbol                                                 
1903    0.000443  0.006928  0.000000  0.012375        A
1904   -0.000690 -0.007873  0.000171  0.014824        A
1905   -0.001354  0.001545  0.000007 -0.008195        C
1906   -0.001578  0.008796 -0.000164  0.015955        D
1907   -0.001578  0.008796 -0.000164  0.015955        A
1909   -0.001354  0.001545  0.000007 -0.008195        B

print(w)
Out[656]: 
  Category  var_1_wgt  var_2_wgt  var_3_wgt  var_4_wgt
0        A   0.182022   0.182022   0.131243   0.182022
1        B   0.534814   0.534814   0.534814   0.534814
2        C   0.131243   0.534814   0.131243   0.182022
3        D   0.182022   0.151921   0.151921   0.131243

Решение

#convert Category to numerical encoding
df['C_Number'] = df.Category.apply(lambda x: ord(x.lower())-97)

#Get a dot product for each row with all category weights and the extract the weights by the category number

df['new_var'] = ((df.iloc[:,:4].values).dot(w.iloc[:,-4:].values))[np.arange(len(df)),df.C_Number]

Out[654]: 
           var_1     var_2     var_3     var_4 Category  C_Number   new_var
Symbol                                                                     
1903    0.000443  0.006928  0.000000  0.012375        A         0  0.006038
1904   -0.000690 -0.007873  0.000171  0.014824        A         0 -0.001615
1905   -0.001354  0.001545  0.000007 -0.008195        C         2 -0.000595
1906   -0.001578  0.008796 -0.000164  0.015955        D         3  0.006481
1907   -0.001578  0.008796 -0.000164  0.015955        A         0  0.007300
1909   -0.001354  0.001545  0.000007 -0.008195        B         1 -0.000661
Другие вопросы по тегам