2016-11-14 3 views
2

У меня есть следующий кадр данных:Присвоить GroupBy применить результат родительского dataframe

df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar','foo', 'bar', 'foo', 'foo'], 
        'B' : ['one', 'one', 'two', 'three', 'two', 'two', 'one', 'three'], 
        'C' : np.random.randn(8), 
        'D' : np.random.randn(8)}) 

    A B C D 
0 foo one 0.478183 -1.267588 
1 bar one 0.555985 -2.143590 
2 foo two -1.592865 1.251546 
3 bar three 0.174138 -0.708198 
4 foo two 0.302215 -0.219041 
5 bar two -0.034550 -0.965414 
6 foo one 1.310828 -0.388601 
7 foo three 0.357659 -1.610443 

Я пытаюсь добавить еще один столбец, который будет нормированная версия колонки C над перегородкой от A:

normed = df.groupby('A').apply(lambda x: (x['C']-min(x['C']))/(max(x['C'])-min(x['C']))) 

A  
bar 1 0.000000 
    3 0.033396 
    5 1.000000 
foo 0 1.000000 
    2 0.413716 
    4 0.000000 
    6 0.441061 
    7 0.357787 

Наконец, я хочу присоединиться к этому результату назад к ФР (с помощью советов из similar question):

df.join(normed, on='A', rsuffix='_normed') 

Однако я получаю сообщение об ошибке:

ValueError: len(left_on) must equal the number of levels in the index of "right"

Как я могу добавить normed результат обратно в dataframe df?

+0

Обратите внимание, что проблема в основном уходит, если вы используете 'transform' вместо' apply'. Также вы можете использовать 'groupby ('A') ['C']' вместо 'groupby ('A')' для более чистого кода. См. Мой ответ ниже для полного синтаксиса. – JohnE

ответ

3

Вы получаете эту ошибку, потому что у вас есть MultiIndex с длиной 2 на первом уровне. Второй уровень - это исходный индекс.

normed.index 

Out[35]: 

MultiIndex(levels=[['bar', 'foo'], [0, 1, 2, 3, 4, 5, 6, 7]], 
      labels=[[0, 0, 0, 1, 1, 1, 1, 1], [1, 3, 5, 0, 2, 4, 6, 7]], 
      names=['A', None]) 

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

normed.index = normed.index.droplevel() 

до прихода:

df.join(normed, rsuffix='_normed') 
+0

Хороший, я не знал метод '.droplevel()' – MMF

+0

Это аккуратное решение. Я думал, что вам нужно будет '.reset_index()' on 'normed', а затем сделать некоторые причудливые изменения макета. Это хороший простой подход к повторному использованию исходного индекса. –

1

Что вы можете сделать, это следующее:

# Get tuples (index, value) for each level 
foo = zip(normed.foo.index, normed.foo.values) 
bar = zip(normed.bar.index, normed.bar.values) 

# Merge the two lists 
foo.extend(bar) # merged lists contained in foo 

# Sort the list 
new_list = sorted(foo, key=lambda x: x[0]) 

# Create new column in dataframe 
index, values = zip(*new_list) # unzip 
df['New_column'] = values 

Выход

Out[85]: 
A  B   C   D New_column 
0 foo one 0.039683 -0.041559 0.638594 
1 bar one -0.090650 -2.316097 0.000000 
2 foo two 0.024210 0.616764 0.629815 
3 bar three 0.142740 0.156198 0.450339 
4 foo two -1.085916 -0.432832 0.000000 
5 bar two 0.427604 -1.154850 1.000000 
6 foo one -0.156424 0.037188 0.527335 
7 foo three 0.676706 -1.336921 1.000000 

NB: Может быть, есть поумнее способ сделать это.

1

Вы должны избавиться от первого уровня мультииндекса, созданного группой first (т.е. «Foo» и «Bar»).

Добавление следующий код должен работать:

normed = normed.reset_index(level=0) 
del normed['A'] 
normed.rename(columns={'C':'C_normed'}, inplace=True) 
pd.concat([df, normed], axis=1) 

Результат:

A B C D C_normed 
0 foo one 1.697923 0.656727 1.000000 
1 bar one -0.626052 -0.466088 0.000000 
2 foo two -0.501440 1.080408 0.000000 
3 bar three 0.731791 -1.531915 1.000000 
4 foo two -0.202666 0.275042 0.135846 
5 bar two -0.340455 -0.737039 0.210332 
6 foo one 0.506664 1.049853 0.458362 
7 foo three -0.358317 -0.598262 0.065075 
2

Самый простой способ применить reset_index к normed

normed = df.groupby('A').apply(lambda x: (x['C']-min(x['C']))/(max(x['C'])-min(x['C']))) 
normed = normed.reset_index(level=0, drop=True) 

А теперь просто добавить normed в колонка до df

df['normed'] = normed 
2

На самом деле существует очень простое решение.Когда GroupBy делает один-на-один операцию (а не уменьшения), вы можете использовать transform и индексация уже позаботилась о вас:

df['c_normed'] = df.groupby('A')['C'].transform(lambda x: (x-min(x))/(max(x)-min(x))) 

Также обратите внимание, что код немного чище, если вы используете df.groupby('A')['C'], потому что тогда вы можете просто использовать x вместо x['C'] внутри лямбда. А также в этом случае использование x['C'] работает с применением, но не преобразуется (я не уверен, почему ...).

+0

Не искажает ли группа порядок? Чтобы быть в безопасности, вы можете добавить a.'sort_index (level = 1) ' –

+0

@ MaartenFabré Спасибо, вы правы. Я обновил свой ответ, так что это уже не проблема. – JohnE

Смежные вопросы