Добавление к окну в пандах - PullRequest
0 голосов
/ 13 июня 2018

У меня есть две панды DataFrames, подобные этой:

category        1         2           3

b              15         35          20        
d              40         35          15                       

category           total

 a                  10 
 b                  10  
 c                  10 
 d                  10  
 e                  10 
 f                  10 

Во втором Dataframe категории уникальны, и в каждой категории есть только одна строка.В первом Dataframe категория может появляться более одного раза.

Я хотел бы добавить элемент в столбце '2' в первом DataFrame к соответствующему элементу во втором DataFrame, элемент в столбце '1'следует добавить в ячейку выше, а столбец «3» - в ячейку ниже.

Отображение этого результата:

category           total

 a                  10 + 15
 b                  10 + 35
 c                  10 + 20 + 40
 d                  10      + 35 
 e                  10      + 15
 f                  10 

Есть ли хороший способ сделать это с помощью панд?У меня очень большой набор данных, поэтому для меня важно, чтобы выбранный мной подход был быстрым и не занимал слишком много памяти.Было бы лучше, если бы я не использовал Панд и использовал вместо этого Numpy?

Ответы [ 2 ]

0 голосов
/ 13 июня 2018

Довольно долго, но не требует циклов:

first = pd.DataFrame([['b', 15, 35, 20], ['d', 40, 35, 15]], columns = ['Category', '1', '2', '3'])

# Category  1    2   3
# 0 b      15   35  20
# 1 d      40   35  15

second = pd.DataFrame( [['a', 10 ],['b', 10],['c', 10 ],['d', 10  ], ['e', 10 ], ['f', 10 ]], columns=['Category', 'total'])

# Category  total
# 0 a      10
# 1 b      10
# 2 c      10
# 3 d      10
# 4 e      10
# 5 f      10

Существует сопоставление для значений выше и ниже.если вы думаете об этом: сопоставление second['Category'] в first['Category'] можно суммировать следующим образом (second['category'] должно иметь различные значения):

second['above_mapped'] = second['Category'].shift(-1)
second['below_mapped'] = second['Category'].shift(1)

print(second)

  Category  total above_mapped below_mapped
0        a     10            b          NaN
1        b     10            c            a
2        c     10            d            b
3        d     10            e            c
4        e     10            f            d
5        f     10          NaN            e

Используя приведенную выше таблицу сопоставления, я могу создать двасловари для определения отображения:

above_map = pd.Series(second['above_mapped'].values, index=second['Category']).to_dict()
# {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e', 'e': 'f', 'f': nan}


below_map = pd.Series(second['below_mapped'].values, index=second['Category']).to_dict()
# {'a': nan, 'b': 'a', 'c': 'b', 'd': 'c', 'e': 'd', 'f': 'e'}

Я могу создать отображение между фактическими значениями для сопоставления, просто используя first dataframe:

above_values = pd.Series(first['1'].values, index=first['Category']).to_dict()
# {'b': 15, 'd': 40}

# middle values just correspond to the 'Category' column of first
middle_values = pd.Series(first['2'].values, index=first['Category']).to_dict()
# {'b': 35, 'd': 35}

below_values = pd.Series(first['3'].values, index=first['Category']).to_dict()
# {'b': 20, 'd': 15}

Теперь сопоставляя числовые значенияsecond с использованием трех вышеупомянутых словарей:

second['above_value_mapped'] = second['above_mapped'].map(above_values)
second['middle_value_mapped']= second['Category'].map(middle_values)
second['below_value_mapped'] = second['below_mapped'].map(below_values)
print(second) 


  Category  total above_mapped below_mapped  above_value_mapped  \
0        a     10            b          NaN                15.0   
1        b     10            c            a                 NaN   
2        c     10            d            b                40.0   
3        d     10            e            c                 NaN   
4        e     10            f            d                 NaN   
5        f     10          NaN            e                 NaN   

   middle_value_mapped  below_value_mapped  
0                  NaN                 NaN  
1                 35.0                 NaN  
2                  NaN                20.0  
3                 35.0                 NaN  
4                  NaN                15.0  
5                  NaN                 NaN 

Суммирование столбцов second['total'], second['above_value_mapped'], second['middle_value_mapped'] и second['below_value_mapped'] дает желаемое значение total:

second.fillna(0, inplace=True)
second['new_total'] = second['total'] + second['above_value_mapped'] + second['middle_value_mapped'] +second['below_value_mapped']
second[['Category', 'new_total']]

Category new_total
0   a    25.0
1   b    45.0
2   c    70.0
3   d    45.0
4   e    25.0
5   f    10.0

Сроки

# Test dataframes
a =np.unique(pd.util.testing.rands_array(4, 10000)) #length 4 distinct strings as categories
# a.shape ~ 10000

df1 = pd.DataFrame(data=np.random.randint(1, 50, (a.shape[0], 3)), 
                   columns=['1', '2', '3'], 
                   index=a)
df2 = pd.DataFrame({'Category': a, 
                    'total': [10]*a.shape[0]})


def akilat90(first, second):

    second['above_mapped'] = second['Category'].shift(-1)
    second['below_mapped'] = second['Category'].shift(1)

    above_map = pd.Series(second['Category'].shift(-1).values, index=second['Category']).to_dict()       

    below_map = pd.Series(second['Category'].shift(1).values, index=second['Category']).to_dict()        

    above_values = pd.Series(first['1'].values, index=first['Category']).to_dict()        

    middle_values = pd.Series(first['2'].values, index=first['Category']).to_dict()        

    below_values = pd.Series(first['3'].values, index=first['Category']).to_dict()        

    second['above_value_mapped'] = second['above_mapped'].map(above_values)
    second['middle_value_mapped']= second['Category'].map(middle_values)
    second['below_value_mapped'] = second['below_mapped'].map(below_values)

    second.fillna(0, inplace=True)
    second['new_total'] = second['total'] + second['above_value_mapped'] + second['middle_value_mapped'] +second['below_value_mapped']
    return second[['Category', 'new_total']]


def Jacquot(df1, df2):
    df2 = df2.set_index('Category')
    accu = np.zeros_like(df2['total'].values.ravel())

    for cat in df1.index.unique():
        idxs = np.where(df2.index == cat)[0]
        for idx in idxs:
            accu[max(idx - 1, 0) : (idx + 2)] += df1.loc[cat].values

    df2['total'] += accu
    return df2



%%timeit
akilat90(df1.reset_index().rename(columns = {'index':'Category'}), df2)
# 33.1 ms ± 1.36 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
Jacquot(df1, df2)
# <Please check>
0 голосов
/ 13 июня 2018

Мне было трудно сделать это полностью векторизованным способом: дело в том, что в df2 одна и та же категория может быть в смежных индексах, и поэтому окна будут перекрываться, что делает цикл (для меня) необходимым.

Как я создал данные:

df1 = pd.DataFrame(data=[[15, 35, 20], 
                         [40, 35, 15]], 
                   columns=[1, 2, 3], 
                   index=['b', 'd'])
df2 = pd.DataFrame({'category': list('abcdef'), 
                    'total': [10] * 6})
df2 = df2.set_index('category')

А затем обрабатывающая часть: массив accu накапливает все значения, которые мы позже добавим в столбец total.

accu = np.zeros_like(df2['total'].values.ravel())

for cat in df1.index.unique():
    idx = df2.index.get_loc(cat)
    accu[max(idx - 1, 0) : (idx + 2)] += np.sum(df1.loc[cat].values, axis=0)

df2['total'] += accu

Конечно, это может быть быстрее с numpy функциями вещания и интеллектуального индексирования, но, несмотря на эффективность памяти, на мой взгляд.Просто скажите мне, если это решение не достаточно быстро для вас, как есть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...