2015-01-07 4 views
1

Если у меня есть панд dataframe, содержащий:Добавление столбца pandas на основе группированных счетчиков другого столбца?

Visited PersonId 
0 GB  1 
1 US  1 
2 US  1 
3 GB  1 
4 DE  1 
5 CN  2 
6 US  2 
7 GB  3 
8 GB  4 

Какой самый простой способ, чтобы добавить новый столбец, который содержит количество уникального числа посещенных стран для каждого PersonId?

например, для вышеуказанного, человек 1 посетил 3 разных страны. Новый dataframe после добавления нового столбца должен выглядеть следующим образом для приведенных выше данных:

Visited PersonId CountryCount 
0 GB  1   3 
1 US  1   3 
2 US  1   3 
3 GB  1   3 
4 DE  1   3 
5 CN  2   2 
6 US  2   2 
7 GB  3   1 
8 GB  4   1 

ответ

3

Это работает, но я чувствую, что есть лучший способ

In [104]: 

df['CountryCount'] = df['PersonId'].map(df.groupby(['PersonId'])['Visited'].unique().apply(len)) 
df 
Out[104]: 
    Visited PersonId CountryCount 
0  GB   1    3 
1  US   1    3 
2  US   1    3 
3  GB   1    3 
4  DE   1    3 
5  CN   2    2 
6  US   2    2 
7  GB   3    1 
8  GB   4    1 
+0

хороший. Я искал что-то вроде функций окна SQL, но еще ничего не нашел –

+1

'.nunique' тоже работает: ' df ['PersonId']. Map (df.groupby (['PersonId']) ['Посещенные '] .nunique()) ' – Primer

+1

@Primer да, я уже прокомментировал это на ответ Романа – EdChum

2

Не знаю, если это может быть более элегантный, но это работает

>>> g = df.groupby('PersonId')['Visited'].nunique().reset_index() 
>>> g.columns = ['PersonId', 'CountryCount'] 
>>> pd.merge(df, g) 
    Visited PersonId CountryCount 
0  GB   1    3 
1  US   1    3 
2  US   1    3 
3  GB   1    3 
4  DE   1    3 
5  CN   2    2 
6  US   2    2 
7  GB   3    1 
8  GB   4    1 

Или, как @EdChum предложил в комментариях, он может быть сокращен до

df['CountryCount'] = df['PersonId'].map(df.groupby('PersonId')['Visited'].nunique()) 

На всякий случай, я проверил время исполнения для всех ответов. Хотя я не думаю, что действительно имеет значение в OP случае, оказалось, что метод @EdChum является явным победителем здесь:

In [7]: %timeit df["CountryCount"] = df.groupby("PersonId")["Visited"].transform(pd.Series.nunique) 
100 loops, best of 3: 2.32 ms per loop 

In [8]: %timeit df['CountryCount'] = df['PersonId'].map(df.groupby('PersonId')['Visited'].nunique()) 
100 loops, best of 3: 2.52 ms per loop 

In [9]: %timeit df['CountryCount'] = df['PersonId'].map(df.groupby(['PersonId'])['Visited'].unique().apply(len)) 
1000 loops, best of 3: 1.29 ms per loop 

После дополнительных испытаний, я обнаружил, что сочетание @DSM и @EdChums работает еще быстрее :)

In [26]: %timeit df["CountryCount"] = df.groupby("PersonId")["Visited"].transform(lambda x: x.unique().size) 
1000 loops, best of 3: 952 µs per loop 

Here's каким-то образом связан вопрос на GitHub.

+1

Вы можете сократить до 'df ['CountryCount'] = df ['PersonId']. Map (df.groupby ('PersonId') ['Посещенные']. Nunique())' и избежать слияния – EdChum

+1

hm yep. Тогда наши ответы будут почти одинаковыми :) –

+1

Я думаю, что использование 'nunique' лучше, чем вызов' apply (len) 'хотя;) +1 – EdChum

2

Если вы хотите, чтобы "трансляции" по группе, вы обычно используете transform:

>>> df["CountryCount"] = df.groupby("PersonId")["Visited"].transform(pd.Series.nunique) 
>>> df 
    Visited PersonId CountryCount 
0  GB   1   3 
1  US   1   3 
2  US   1   3 
3  GB   1   3 
4  DE   1   3 
5  CN   2   2 
6  US   2   2 
7  GB   3   1 
8  GB   4   1 
+0

Я думаю, что преобразование является правильным способ использования здесь, и это также самое быстрое, если мы используем 'unique(). size' вместо' nunique() ' –