Por “agrupar por” nos referimos a un proceso que involucra uno o más de los siguientes pasos:
Terrible los datos en grupos basados en algunos criterios.
Aplicando una función para cada grupo de forma independiente.
Combinatorio los resultados en una estructura de datos.
De estos, el paso dividido es el más sencillo. De hecho, en muchas situaciones es posible que deseemos dividir el conjunto de datos en grupos y hacer algo con esos grupos. En el paso de aplicación, es posible que deseemos realizar una de las siguientes acciones:
Agregación: calcula una estadística de resumen (o estadísticas) para cada grupo. Algunos ejemplos:
Calcule las sumas o medias del grupo. Calcule los tamaños / recuentos de grupos.
Calcule las sumas o medias del grupo.
Calcule los tamaños / recuentos de grupos.
Transformación: realiza algunos cálculos específicos del grupo y devuelve un objeto indexado similar. Algunos ejemplos:
Estandarizar datos (zscore) dentro de un grupo. Llenar las NA dentro de los grupos con un valor derivado de cada grupo.
Estandarizar datos (zscore) dentro de un grupo.
Llenar las NA dentro de los grupos con un valor derivado de cada grupo.
Filtración: descartar algunos grupos, de acuerdo con un cálculo grupal que evalúa Verdadero o Falso. Algunos ejemplos:
Descarte los datos que pertenezcan a grupos con pocos miembros. Filtre los datos según la suma o la media del grupo.
Descarte los datos que pertenezcan a grupos con pocos miembros.
Filtre los datos según la suma o la media del grupo.
Alguna combinación de lo anterior: GroupBy examinará los resultados del paso de aplicación e intentará devolver un resultado sensiblemente combinado si no encaja en ninguna de las dos categorías anteriores.
Dado que el conjunto de métodos de instancia de objeto en las estructuras de datos de pandas es generalmente rico y expresivo, a menudo simplemente queremos invocar, digamos, una función DataFrame en cada grupo. El nombre GroupBy debería ser bastante familiar para aquellos que han utilizado una herramienta basada en SQL (o itertools), en el que puede escribir código como:
itertools
SELECT Column1, Column2, mean(Column3), sum(Column4) FROM SomeTable GROUP BY Column1, Column2
Nuestro objetivo es hacer que operaciones como esta sean naturales y fáciles de expresar con pandas. Abordaremos cada área de la funcionalidad de GroupBy y luego proporcionaremos algunos ejemplos / casos de uso no triviales.
Ver el libro de cocina para algunas estrategias avanzadas.
Los objetos pandas se pueden dividir en cualquiera de sus ejes. La definición abstracta de agrupación es proporcionar un mapeo de etiquetas a nombres de grupos. Para crear un objeto GroupBy (más sobre qué es el objeto GroupBy más adelante), puede hacer lo siguiente:
In [1]: df = pd.DataFrame( ...: [ ...: ("bird", "Falconiformes", 389.0), ...: ("bird", "Psittaciformes", 24.0), ...: ("mammal", "Carnivora", 80.2), ...: ("mammal", "Primates", np.nan), ...: ("mammal", "Carnivora", 58), ...: ], ...: index=["falcon", "parrot", "lion", "monkey", "leopard"], ...: columns=("class", "order", "max_speed"), ...: ) ...: In [2]: df Out[2]: class order max_speed falcon bird Falconiformes 389.0 parrot bird Psittaciformes 24.0 lion mammal Carnivora 80.2 monkey mammal Primates NaN leopard mammal Carnivora 58.0 # default is axis=0 In [3]: grouped = df.groupby("class") In [4]: grouped = df.groupby("order", axis="columns") In [5]: grouped = df.groupby(["class", "order"])
El mapeo se puede especificar de muchas formas diferentes:
Una función de Python, que se llamará en cada una de las etiquetas de los ejes.
Una lista o matriz NumPy de la misma longitud que el eje seleccionado.
Un dict o Series, proporcionando un label -> group name cartografía.
Series
label -> group name
Para DataFrame objetos, una cadena que indica un nombre de columna o un nombre de nivel de índice que se utilizará para agrupar.
DataFrame
df.groupby('A') es solo azúcar sintáctico para df.groupby(df['A']).
df.groupby('A')
df.groupby(df['A'])
Una lista de cualquiera de las cosas anteriores.
Colectivamente nos referimos a los objetos de agrupación como el teclas. Por ejemplo, considere lo siguiente DataFrame:
Nota
Una cadena pasada a groupby puede referirse a una columna o un nivel de índice. Si una cadena coincide con un nombre de columna y un nombre de nivel de índice, ValueError se levantará.
groupby
ValueError
In [6]: 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), ...: } ...: ) ...: In [7]: df Out[7]: A B C D 0 foo one 0.469112 -0.861849 1 bar one -0.282863 -2.104569 2 foo two -1.509059 -0.494929 3 bar three -1.135632 1.071804 4 foo two 1.212112 0.721555 5 bar two -0.173215 -0.706771 6 foo one 0.119209 -1.039575 7 foo three -1.044236 0.271860
En un DataFrame, obtenemos un objeto GroupBy llamando groupby(). Naturalmente, podríamos agrupar por el A o B columnas, o ambas:
groupby()
A
B
In [8]: grouped = df.groupby("A") In [9]: grouped = df.groupby(["A", "B"])
Nuevo en la versión 0.24.
Si también tenemos un MultiIndex en columnas A y B, podemos agrupar por todas las columnas excepto las especificadas
In [10]: df2 = df.set_index(["A", "B"]) In [11]: grouped = df2.groupby(level=df2.index.names.difference(["B"])) In [12]: grouped.sum() Out[12]: C D A bar -1.591710 -1.739537 foo -0.752861 -1.402938
Estos dividirán el DataFrame en su índice (filas). También podríamos dividir por las columnas:
In [13]: def get_letter_type(letter): ....: if letter.lower() in 'aeiou': ....: return 'vowel' ....: else: ....: return 'consonant' ....: In [14]: grouped = df.groupby(get_letter_type, axis=1)
pandas Index los objetos admiten valores duplicados. Si se utiliza un índice no único como clave de grupo en una operación groupby, todos los valores para el mismo valor de índice se considerarán en un grupo y, por lo tanto, la salida de las funciones de agregación solo contendrá valores de índice únicos:
Index
In [15]: lst = [1, 2, 3, 1, 2, 3] In [16]: s = pd.Series([1, 2, 3, 10, 20, 30], lst) In [17]: grouped = s.groupby(level=0) In [18]: grouped.first() Out[18]: 1 1 2 2 3 3 dtype: int64 In [19]: grouped.last() Out[19]: 1 10 2 20 3 30 dtype: int64 In [20]: grouped.sum() Out[20]: 1 11 2 22 3 33 dtype: int64
Tenga en cuenta que no ocurre división hasta que sea necesario. La creación del objeto GroupBy solo verifica que haya pasado una asignación válida.
Se pueden expresar muchos tipos de manipulaciones de datos complicadas en términos de operaciones GroupBy (aunque no se puede garantizar que sean las más eficientes). Puede ser bastante creativo con las funciones de asignación de etiquetas.
Por defecto, las claves de grupo se ordenan durante el groupby operación. Sin embargo, puede pasar sort=False para posibles aceleraciones:
sort=False
In [21]: df2 = pd.DataFrame({"X": ["B", "B", "A", "A"], "Y": [1, 2, 3, 4]}) In [22]: df2.groupby(["X"]).sum() Out[22]: Y X A 7 B 3 In [23]: df2.groupby(["X"], sort=False).sum() Out[23]: Y X B 3 A 7
Tenga en cuenta que groupby preservará el orden en el que observaciones están ordenados dentro de cada grupo. Por ejemplo, los grupos creados por groupby() a continuación están en el orden en que aparecieron en el original DataFrame:
In [24]: df3 = pd.DataFrame({"X": ["A", "B", "A", "B"], "Y": [1, 4, 3, 2]}) In [25]: df3.groupby(["X"]).get_group("A") Out[25]: X Y 0 A 1 2 A 3 In [26]: df3.groupby(["X"]).get_group("B") Out[26]: X Y 1 B 4 3 B 2
Nuevo en la versión 1.1.0.
Por defecto NA Los valores se excluyen de las claves de grupo durante el groupby operación. Sin embargo, en caso de que desee incluir NA valores en claves de grupo, podría pasar dropna=False para lograrlo.
NA
dropna=False
In [27]: df_list = [[1, 2, 3], [1, None, 4], [2, 1, 3], [1, 2, 2]] In [28]: df_dropna = pd.DataFrame(df_list, columns=["a", "b", "c"]) In [29]: df_dropna Out[29]: a b c 0 1 2.0 3 1 1 NaN 4 2 2 1.0 3 3 1 2.0 2
# Default ``dropna`` is set to True, which will exclude NaNs in keys In [30]: df_dropna.groupby(by=["b"], dropna=True).sum() Out[30]: a c b 1.0 2 3 2.0 2 5 # In order to allow NaN in keys, set ``dropna`` to False In [31]: df_dropna.groupby(by=["b"], dropna=False).sum() Out[31]: a c b 1.0 2 3 2.0 2 5 NaN 1 4
La configuración predeterminada de dropna el argumento es True lo que significa NA no se incluyen en las claves de grupo.
dropna
True
los groups El atributo es un dictado cuyas claves son los grupos únicos calculados y los valores correspondientes son las etiquetas de los ejes que pertenecen a cada grupo. En el ejemplo anterior tenemos:
groups
In [32]: df.groupby("A").groups Out[32]: {'bar': [1, 3, 5], 'foo': [0, 2, 4, 6, 7]} In [33]: df.groupby(get_letter_type, axis=1).groups Out[33]: {'consonant': ['B', 'C', 'D'], 'vowel': ['A']}
Llamar al Python estándar len función en el objeto GroupBy simplemente devuelve la longitud de la groups dict, por lo que en gran parte es solo una conveniencia:
len
In [34]: grouped = df.groupby(["A", "B"]) In [35]: grouped.groups Out[35]: {('bar', 'one'): [1], ('bar', 'three'): [3], ('bar', 'two'): [5], ('foo', 'one'): [0, 6], ('foo', 'three'): [7], ('foo', 'two'): [2, 4]} In [36]: len(grouped) Out[36]: 6
GroupBy tabulará los nombres completos de las columnas (y otros atributos):
GroupBy
In [37]: df Out[37]: height weight gender 2000-01-01 42.849980 157.500553 male 2000-01-02 49.607315 177.340407 male 2000-01-03 56.293531 171.524640 male 2000-01-04 48.421077 144.251986 female 2000-01-05 46.556882 152.526206 male 2000-01-06 68.448851 168.272968 female 2000-01-07 70.757698 136.431469 male 2000-01-08 58.909500 176.499753 female 2000-01-09 76.435631 174.094104 female 2000-01-10 45.306120 177.540920 male In [38]: gb = df.groupby("gender")
In [39]: gb.<TAB> # noqa: E225, E999 gb.agg gb.boxplot gb.cummin gb.describe gb.filter gb.get_group gb.height gb.last gb.median gb.ngroups gb.plot gb.rank gb.std gb.transform gb.aggregate gb.count gb.cumprod gb.dtype gb.first gb.groups gb.hist gb.max gb.min gb.nth gb.prod gb.resample gb.sum gb.var gb.apply gb.cummax gb.cumsum gb.fillna gb.gender gb.head gb.indices gb.mean gb.name gb.ohlc gb.quantile gb.size gb.tail gb.weight
Con datos indexados jerárquicamente, es bastante natural agrupar por uno de los niveles de la jerarquía.
Creemos una serie con dos niveles MultiIndex.
MultiIndex
In [40]: arrays = [ ....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ....: ["one", "two", "one", "two", "one", "two", "one", "two"], ....: ] ....: In [41]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"]) In [42]: s = pd.Series(np.random.randn(8), index=index) In [43]: s Out[43]: first second bar one -0.919854 two -0.042379 baz one 1.247642 two -0.009920 foo one 0.290213 two 0.495767 qux one 0.362949 two 1.548106 dtype: float64
Luego podemos agrupar por uno de los niveles en s.
s
In [44]: grouped = s.groupby(level=0) In [45]: grouped.sum() Out[45]: first bar -0.962232 baz 1.237723 foo 0.785980 qux 1.911055 dtype: float64
Si el MultiIndex tiene nombres especificados, estos se pueden pasar en lugar del número de nivel:
In [46]: s.groupby(level="second").sum() Out[46]: second one 0.980950 two 1.991575 dtype: float64
Las funciones de agregación como sum tomará el parámetro de nivel directamente. Además, el índice resultante se denominará según el nivel elegido:
sum
In [47]: s.sum(level="second") Out[47]: second one 0.980950 two 1.991575 dtype: float64
Se admite la agrupación con varios niveles.
In [48]: s Out[48]: first second third bar doo one -1.131345 two -0.089329 baz bee one 0.337863 two -0.945867 foo bop one -0.932132 two 1.956030 qux bop one 0.017587 two -0.016692 dtype: float64 In [49]: s.groupby(level=["first", "second"]).sum() Out[49]: first second bar doo -1.220674 baz bee -0.608004 foo bop 1.023898 qux bop 0.000895 dtype: float64
Los nombres de nivel de índice se pueden proporcionar como claves.
In [50]: s.groupby(["first", "second"]).sum() Out[50]: first second bar doo -1.220674 baz bee -0.608004 foo bop 1.023898 qux bop 0.000895 dtype: float64
Más sobre el sum función y agregación más tarde.
Un DataFrame puede agruparse mediante una combinación de columnas y niveles de índice especificando los nombres de las columnas como cadenas y los niveles de índice como pd.Grouper objetos.
pd.Grouper
In [51]: arrays = [ ....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"], ....: ["one", "two", "one", "two", "one", "two", "one", "two"], ....: ] ....: In [52]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"]) In [53]: df = pd.DataFrame({"A": [1, 1, 1, 1, 2, 2, 3, 3], "B": np.arange(8)}, index=index) In [54]: df Out[54]: A B first second bar one 1 0 two 1 1 baz one 1 2 two 1 3 foo one 2 4 two 2 5 qux one 3 6 two 3 7
Los siguientes grupos de ejemplo df por el second nivel de índice y el A columna.
df
second
In [55]: df.groupby([pd.Grouper(level=1), "A"]).sum() Out[55]: B second A one 1 2 2 4 3 6 two 1 4 2 5 3 7
Los niveles de índice también se pueden especificar por nombre.
In [56]: df.groupby([pd.Grouper(level="second"), "A"]).sum() Out[56]: B second A one 1 2 2 4 3 6 two 1 4 2 5 3 7
Los nombres de nivel de índice se pueden especificar como claves directamente para groupby.
In [57]: df.groupby(["second", "A"]).sum() Out[57]: B second A one 1 2 2 4 3 6 two 1 4 2 5 3 7
Una vez que haya creado el objeto GroupBy a partir de un DataFrame, es posible que desee hacer algo diferente para cada una de las columnas. Por lo tanto, usando [] similar a obtener una columna de un DataFrame, puede hacer:
[]
In [58]: grouped = df.groupby(["A"]) In [59]: grouped_C = grouped["C"] In [60]: grouped_D = grouped["D"]
Esto es principalmente azúcar sintáctico para la alternativa y mucho más detallado:
In [61]: df["C"].groupby(df["A"]) Out[61]: <pandas.core.groupby.generic.SeriesGroupBy object at 0x7fa941fbb370>
Además, este método evita volver a calcular la información de agrupación interna derivada de la clave pasada.
Con el objeto GroupBy en la mano, iterar a través de los datos agrupados es muy natural y funciona de manera similar a itertools.groupby():
itertools.groupby()
In [62]: grouped = df.groupby('A') In [63]: for name, group in grouped: ....: print(name) ....: print(group) ....: bar A B C D 1 bar one 0.254161 1.511763 3 bar three 0.215897 -0.990582 5 bar two -0.077118 1.211526 foo A B C D 0 foo one -0.575247 1.346061 2 foo two -1.143704 1.627081 4 foo two 1.193555 -0.441652 6 foo one -0.408530 0.268520 7 foo three -0.862495 0.024580
En el caso de agrupar por múltiples claves, el nombre del grupo será una tupla:
In [64]: for name, group in df.groupby(['A', 'B']): ....: print(name) ....: print(group) ....: ('bar', 'one') A B C D 1 bar one 0.254161 1.511763 ('bar', 'three') A B C D 3 bar three 0.215897 -0.990582 ('bar', 'two') A B C D 5 bar two -0.077118 1.211526 ('foo', 'one') A B C D 0 foo one -0.575247 1.346061 6 foo one -0.408530 0.268520 ('foo', 'three') A B C D 7 foo three -0.862495 0.02458 ('foo', 'two') A B C D 2 foo two -1.143704 1.627081 4 foo two 1.193555 -0.441652
Ver Iterando a través de grupos.
Se puede seleccionar un solo grupo usando get_group():
get_group()
In [65]: grouped.get_group("bar") Out[65]: A B C D 1 bar one 0.254161 1.511763 3 bar three 0.215897 -0.990582 5 bar two -0.077118 1.211526
O para un objeto agrupado en varias columnas:
In [66]: df.groupby(["A", "B"]).get_group(("bar", "one")) Out[66]: A B C D 1 bar one 0.254161 1.511763
Una vez que se ha creado el objeto GroupBy, hay varios métodos disponibles para realizar un cálculo sobre los datos agrupados. Estas operaciones son similares a las API de agregación, API de ventana, y volver a muestrear API.
Uno obvio es la agregación a través de la aggregate() o equivalente agg() método:
aggregate()
agg()
In [67]: grouped = df.groupby("A") In [68]: grouped.aggregate(np.sum) Out[68]: C D A bar 0.392940 1.732707 foo -1.796421 2.824590 In [69]: grouped = df.groupby(["A", "B"]) In [70]: grouped.aggregate(np.sum) Out[70]: C D A B bar one 0.254161 1.511763 three 0.215897 -0.990582 two -0.077118 1.211526 foo one -0.983776 1.614581 three -0.862495 0.024580 two 0.049851 1.185429
Como puede ver, el resultado de la agregación tendrá los nombres de los grupos como el nuevo índice a lo largo del eje agrupado. En el caso de varias claves, el resultado es un MultiIndex por defecto, aunque esto se puede cambiar usando el as_index opción:
as_index
In [71]: grouped = df.groupby(["A", "B"], as_index=False) In [72]: grouped.aggregate(np.sum) Out[72]: A B C D 0 bar one 0.254161 1.511763 1 bar three 0.215897 -0.990582 2 bar two -0.077118 1.211526 3 foo one -0.983776 1.614581 4 foo three -0.862495 0.024580 5 foo two 0.049851 1.185429 In [73]: df.groupby("A", as_index=False).sum() Out[73]: A C D 0 bar 0.392940 1.732707 1 foo -1.796421 2.824590
Tenga en cuenta que puede utilizar el reset_index La función DataFrame para lograr el mismo resultado que los nombres de las columnas se almacenan en el resultado MultiIndex:
reset_index
In [74]: df.groupby(["A", "B"]).sum().reset_index() Out[74]: A B C D 0 bar one 0.254161 1.511763 1 bar three 0.215897 -0.990582 2 bar two -0.077118 1.211526 3 foo one -0.983776 1.614581 4 foo three -0.862495 0.024580 5 foo two 0.049851 1.185429
Otro ejemplo de agregación simple es calcular el tamaño de cada grupo. Esto se incluye en GroupBy como el size método. Devuelve una Serie cuyo índice son los nombres de los grupos y cuyos valores son los tamaños de cada grupo.
size
In [75]: grouped.size() Out[75]: A B size 0 bar one 1 1 bar three 1 2 bar two 1 3 foo one 2 4 foo three 1 5 foo two 2
In [76]: grouped.describe() Out[76]: C ... D count mean std min 25% 50% ... std min 25% 50% 75% max 0 1.0 0.254161 NaN 0.254161 0.254161 0.254161 ... NaN 1.511763 1.511763 1.511763 1.511763 1.511763 1 1.0 0.215897 NaN 0.215897 0.215897 0.215897 ... NaN -0.990582 -0.990582 -0.990582 -0.990582 -0.990582 2 1.0 -0.077118 NaN -0.077118 -0.077118 -0.077118 ... NaN 1.211526 1.211526 1.211526 1.211526 1.211526 3 2.0 -0.491888 0.117887 -0.575247 -0.533567 -0.491888 ... 0.761937 0.268520 0.537905 0.807291 1.076676 1.346061 4 1.0 -0.862495 NaN -0.862495 -0.862495 -0.862495 ... NaN 0.024580 0.024580 0.024580 0.024580 0.024580 5 2.0 0.024925 1.652692 -1.143704 -0.559389 0.024925 ... 1.462816 -0.441652 0.075531 0.592714 1.109898 1.627081 [6 rows x 16 columns]
Otro ejemplo de agregación es calcular el número de valores únicos de cada grupo. Esto es similar al value_counts función, excepto que solo cuenta valores únicos.
value_counts
In [77]: ll = [['foo', 1], ['foo', 2], ['foo', 2], ['bar', 1], ['bar', 1]] In [78]: df4 = pd.DataFrame(ll, columns=["A", "B"]) In [79]: df4 Out[79]: A B 0 foo 1 1 foo 2 2 foo 2 3 bar 1 4 bar 1 In [80]: df4.groupby("A")["B"].nunique() Out[80]: A bar 1 foo 2 Name: B, dtype: int64
Funciones de agregación no devolver los grupos sobre los que está agregando si tienen nombre columnas, cuando as_index=True, el valor por defecto. Las columnas agrupadas serán las índices del objeto devuelto.
as_index=True
Paso as_index=False voluntad devolver los grupos sobre los que está agregando, si se nombran columnas.
as_index=False
Las funciones de agregación son las que reducen la dimensión de los objetos devueltos. Algunas funciones de agregación comunes se tabulan a continuación:
Función
Descripción
mean()
Calcular la media de los grupos
sum()
Calcular la suma de los valores del grupo
size()
Calcular tamaños de grupos
count()
Calcular el recuento del grupo
std()
Desviación estándar de grupos
var()
Calcular la varianza de los grupos
sem()
Error estándar del media de grupos
describe()
Genera estadísticas descriptivas
first()
Calcule el primero de los valores del grupo
last()
Calcular el último de los valores del grupo
nth()
Tome el valor n-ésimo, o un subconjunto si n es una lista
min()
Calcular el mínimo de valores de grupo
max()
Calcular el máximo de valores de grupo
Las funciones de agregación anteriores excluirán los valores NA. Cualquier función que reduzca un Series a un valor escalar es una función de agregación y funcionará, un ejemplo trivial es df.groupby('A').agg(lambda ser: 1). Tenga en cuenta que nth() puede actuar como reductor o un filtro, ver aquí.
df.groupby('A').agg(lambda ser: 1)
Con agrupados Series también puede pasar una lista o dictado de funciones para realizar la agregación, generando un DataFrame:
In [81]: grouped = df.groupby("A") In [82]: grouped["C"].agg([np.sum, np.mean, np.std]) Out[82]: sum mean std A bar 0.392940 0.130980 0.181231 foo -1.796421 -0.359284 0.912265
En un grupo DataFrame, puede pasar una lista de funciones para aplicar a cada columna, lo que produce un resultado agregado con un índice jerárquico:
In [83]: grouped.agg([np.sum, np.mean, np.std]) Out[83]: C D sum mean std sum mean std A bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330 foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
Las agregaciones resultantes reciben el nombre de las funciones en sí. Si necesita cambiar el nombre, puede agregar una operación encadenada para un Series como esto:
In [84]: ( ....: grouped["C"] ....: .agg([np.sum, np.mean, np.std]) ....: .rename(columns={"sum": "foo", "mean": "bar", "std": "baz"}) ....: ) ....: Out[84]: foo bar baz A bar 0.392940 0.130980 0.181231 foo -1.796421 -0.359284 0.912265
Para un grupo DataFrame, puede cambiar el nombre de una manera similar:
In [85]: ( ....: grouped.agg([np.sum, np.mean, np.std]).rename( ....: columns={"sum": "foo", "mean": "bar", "std": "baz"} ....: ) ....: ) ....: Out[85]: C D foo bar baz foo bar baz A bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330 foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
En general, los nombres de las columnas de salida deben ser únicos. No puede aplicar la misma función (o dos funciones con el mismo nombre) a la misma columna.
In [86]: grouped["C"].agg(["sum", "sum"]) Out[86]: sum sum A bar 0.392940 0.392940 foo -1.796421 -1.796421
pandas lo hace le permite proporcionar múltiples lambdas. En este caso, los pandas alterarán el nombre de las funciones lambda (sin nombre), agregando _<i> a cada lambda subsiguiente.
_<i>
In [87]: grouped["C"].agg([lambda x: x.max() - x.min(), lambda x: x.median() - x.mean()]) Out[87]: <lambda_0> <lambda_1> A bar 0.331279 0.084917 foo 2.337259 -0.215962
Nuevo en la versión 0.25.0.
Para admitir la agregación específica de columnas con control sobre los nombres de las columnas de salida, pandas acepta la sintaxis especial en GroupBy.agg(), conocida como “agregación con nombre”, donde
GroupBy.agg()
Las palabras clave son las producción nombres de columna
Los valores son tuplas cuyo primer elemento es la columna a seleccionar y el segundo elemento es la agregación a aplicar a esa columna. pandas proporciona el pandas.NamedAgg namedtuple con los campos ['column', 'aggfunc'] para aclarar cuáles son los argumentos. Como es habitual, la agregación puede ser un alias invocable o de cadena.
pandas.NamedAgg
['column', 'aggfunc']
In [88]: animals = pd.DataFrame( ....: { ....: "kind": ["cat", "dog", "cat", "dog"], ....: "height": [9.1, 6.0, 9.5, 34.0], ....: "weight": [7.9, 7.5, 9.9, 198.0], ....: } ....: ) ....: In [89]: animals Out[89]: kind height weight 0 cat 9.1 7.9 1 dog 6.0 7.5 2 cat 9.5 9.9 3 dog 34.0 198.0 In [90]: animals.groupby("kind").agg( ....: min_height=pd.NamedAgg(column="height", aggfunc="min"), ....: max_height=pd.NamedAgg(column="height", aggfunc="max"), ....: average_weight=pd.NamedAgg(column="weight", aggfunc=np.mean), ....: ) ....: Out[90]: min_height max_height average_weight kind cat 9.1 9.5 8.90 dog 6.0 34.0 102.75
pandas.NamedAgg es solo un namedtuple. También se permiten tuplas simples.
namedtuple
In [91]: animals.groupby("kind").agg( ....: min_height=("height", "min"), ....: max_height=("height", "max"), ....: average_weight=("weight", np.mean), ....: ) ....: Out[91]: min_height max_height average_weight kind cat 9.1 9.5 8.90 dog 6.0 34.0 102.75
Si los nombres de las columnas de salida que desea no son palabras clave de Python válidas, construya un diccionario y descomprima los argumentos de las palabras clave
In [92]: animals.groupby("kind").agg( ....: **{ ....: "total weight": pd.NamedAgg(column="weight", aggfunc=sum) ....: } ....: ) ....: Out[92]: total weight kind cat 17.8 dog 205.5
Los argumentos de palabras clave adicionales no se transfieren a las funciones de agregación. Solo pares de (column, aggfunc) debe pasarse como **kwargs. Si sus funciones de agregación requieren argumentos adicionales, aplíquelos parcialmente con functools.partial().
(column, aggfunc)
**kwargs
functools.partial()
Para Python 3.5 y versiones anteriores, el orden de **kwargs en funciones no se conservó. Esto significa que el orden de las columnas de salida no sería coherente. Para garantizar un orden coherente, las claves (y por tanto las columnas de salida) siempre se ordenarán para Python 3.5.
La agregación con nombre también es válida para agregaciones de series groupby. En este caso, no hay selección de columnas, por lo que los valores son solo las funciones.
In [93]: animals.groupby("kind").height.agg( ....: min_height="min", ....: max_height="max", ....: ) ....: Out[93]: min_height max_height kind cat 9.1 9.5 dog 6.0 34.0
Pasando un dictado a aggregate puede aplicar una agregación diferente a las columnas de un DataFrame:
aggregate
In [94]: grouped.agg({"C": np.sum, "D": lambda x: np.std(x, ddof=1)}) Out[94]: C D A bar 0.392940 1.366330 foo -1.796421 0.884785
Los nombres de las funciones también pueden ser cadenas. Para que una cadena sea válida, debe estar implementada en GroupBy o estar disponible a través de despachando:
In [95]: grouped.agg({"C": "sum", "D": "std"}) Out[95]: C D A bar 0.392940 1.366330 foo -1.796421 0.884785
Algunas agregaciones comunes, actualmente solo sum, mean, std, y sem, han optimizado las implementaciones de Cython:
mean
std
sem
In [96]: df.groupby("A").sum() Out[96]: C D A bar 0.392940 1.732707 foo -1.796421 2.824590 In [97]: df.groupby(["A", "B"]).mean() Out[97]: C D A B bar one 0.254161 1.511763 three 0.215897 -0.990582 two -0.077118 1.211526 foo one -0.491888 0.807291 three -0.862495 0.024580 two 0.024925 0.592714
Por supuesto sum y mean se implementan en objetos pandas, por lo que el código anterior funcionaría incluso sin las versiones especiales a través del envío (ver más abajo).
los transform El método devuelve un objeto que está indexado del mismo (mismo tamaño) que el que se está agrupando. La función de transformación debe:
transform
Devuelve un resultado que sea del mismo tamaño que el fragmento del grupo o que se pueda transmitir al tamaño del fragmento del grupo (p. Ej., Un escalar, grouped.transform(lambda x: x.iloc[-1])).
grouped.transform(lambda x: x.iloc[-1])
Opere columna por columna en el fragmento del grupo. La transformación se aplica al primer fragmento de grupo mediante chunk.apply.
No realizar operaciones in situ en el fragmento de grupo. Los fragmentos de grupo deben tratarse como inmutables y los cambios en un fragmento de grupo pueden producir resultados inesperados. Por ejemplo, al usar fillna, inplace debe ser False (grouped.transform(lambda x: x.fillna(inplace=False))).
fillna
inplace
False
grouped.transform(lambda x: x.fillna(inplace=False))
(Opcionalmente) opera en todo el grupo. Si esto es compatible, se utiliza una ruta rápida a partir de la segundo pedazo.
Por ejemplo, supongamos que deseamos estandarizar los datos dentro de cada grupo:
In [98]: index = pd.date_range("10/1/1999", periods=1100) In [99]: ts = pd.Series(np.random.normal(0.5, 2, 1100), index) In [100]: ts = ts.rolling(window=100, min_periods=100).mean().dropna() In [101]: ts.head() Out[101]: 2000-01-08 0.779333 2000-01-09 0.778852 2000-01-10 0.786476 2000-01-11 0.782797 2000-01-12 0.798110 Freq: D, dtype: float64 In [102]: ts.tail() Out[102]: 2002-09-30 0.660294 2002-10-01 0.631095 2002-10-02 0.673601 2002-10-03 0.709213 2002-10-04 0.719369 Freq: D, dtype: float64 In [103]: transformed = ts.groupby(lambda x: x.year).transform( .....: lambda x: (x - x.mean()) / x.std() .....: ) .....:
Esperaríamos que el resultado ahora tenga una media de 0 y una desviación estándar de 1 dentro de cada grupo, lo que podemos verificar fácilmente:
# Original Data In [104]: grouped = ts.groupby(lambda x: x.year) In [105]: grouped.mean() Out[105]: 2000 0.442441 2001 0.526246 2002 0.459365 dtype: float64 In [106]: grouped.std() Out[106]: 2000 0.131752 2001 0.210945 2002 0.128753 dtype: float64 # Transformed Data In [107]: grouped_trans = transformed.groupby(lambda x: x.year) In [108]: grouped_trans.mean() Out[108]: 2000 1.167126e-15 2001 2.190637e-15 2002 1.088580e-15 dtype: float64 In [109]: grouped_trans.std() Out[109]: 2000 1.0 2001 1.0 2002 1.0 dtype: float64
También podemos comparar visualmente los conjuntos de datos originales y transformados.
In [110]: compare = pd.DataFrame({"Original": ts, "Transformed": transformed}) In [111]: compare.plot() Out[111]: <AxesSubplot:>
Las funciones de transformación que tienen salidas de menor dimensión se transmiten para que coincidan con la forma de la matriz de entrada.
In [112]: ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min()) Out[112]: 2000-01-08 0.623893 2000-01-09 0.623893 2000-01-10 0.623893 2000-01-11 0.623893 2000-01-12 0.623893 ... 2002-09-30 0.558275 2002-10-01 0.558275 2002-10-02 0.558275 2002-10-03 0.558275 2002-10-04 0.558275 Freq: D, Length: 1001, dtype: float64
Alternativamente, los métodos incorporados podrían usarse para producir los mismos resultados.
In [113]: max = ts.groupby(lambda x: x.year).transform("max") In [114]: min = ts.groupby(lambda x: x.year).transform("min") In [115]: max - min Out[115]: 2000-01-08 0.623893 2000-01-09 0.623893 2000-01-10 0.623893 2000-01-11 0.623893 2000-01-12 0.623893 ... 2002-09-30 0.558275 2002-10-01 0.558275 2002-10-02 0.558275 2002-10-03 0.558275 2002-10-04 0.558275 Freq: D, Length: 1001, dtype: float64
Otra transformación de datos común es reemplazar los datos faltantes con la media del grupo.
In [116]: data_df Out[116]: A B C 0 1.539708 -1.166480 0.533026 1 1.302092 -0.505754 NaN 2 -0.371983 1.104803 -0.651520 3 -1.309622 1.118697 -1.161657 4 -1.924296 0.396437 0.812436 .. ... ... ... 995 -0.093110 0.683847 -0.774753 996 -0.185043 1.438572 NaN 997 -0.394469 -0.642343 0.011374 998 -1.174126 1.857148 NaN 999 0.234564 0.517098 0.393534 [1000 rows x 3 columns] In [117]: countries = np.array(["US", "UK", "GR", "JP"]) In [118]: key = countries[np.random.randint(0, 4, 1000)] In [119]: grouped = data_df.groupby(key) # Non-NA count in each group In [120]: grouped.count() Out[120]: A B C GR 209 217 189 JP 240 255 217 UK 216 231 193 US 239 250 217 In [121]: transformed = grouped.transform(lambda x: x.fillna(x.mean()))
Podemos verificar que las medias del grupo no han cambiado en los datos transformados y que los datos transformados no contienen NA.
In [122]: grouped_trans = transformed.groupby(key) In [123]: grouped.mean() # original group means Out[123]: A B C GR -0.098371 -0.015420 0.068053 JP 0.069025 0.023100 -0.077324 UK 0.034069 -0.052580 -0.116525 US 0.058664 -0.020399 0.028603 In [124]: grouped_trans.mean() # transformation did not change group means Out[124]: A B C GR -0.098371 -0.015420 0.068053 JP 0.069025 0.023100 -0.077324 UK 0.034069 -0.052580 -0.116525 US 0.058664 -0.020399 0.028603 In [125]: grouped.count() # original has some missing data points Out[125]: A B C GR 209 217 189 JP 240 255 217 UK 216 231 193 US 239 250 217 In [126]: grouped_trans.count() # counts after transformation Out[126]: A B C GR 228 228 228 JP 267 267 267 UK 247 247 247 US 258 258 258 In [127]: grouped_trans.size() # Verify non-NA count equals group size Out[127]: GR 228 JP 267 UK 247 US 258 dtype: int64
Algunas funciones transformarán automáticamente la entrada cuando se apliquen a un objeto GroupBy, pero devolverán un objeto de la misma forma que el original. Paso as_index=False no afectará a estos métodos de transformación.
Por ejemplo: fillna, ffill, bfill, shift..
fillna, ffill, bfill, shift.
In [128]: grouped.ffill() Out[128]: A B C 0 1.539708 -1.166480 0.533026 1 1.302092 -0.505754 0.533026 2 -0.371983 1.104803 -0.651520 3 -1.309622 1.118697 -1.161657 4 -1.924296 0.396437 0.812436 .. ... ... ... 995 -0.093110 0.683847 -0.774753 996 -0.185043 1.438572 -0.774753 997 -0.394469 -0.642343 0.011374 998 -1.174126 1.857148 -0.774753 999 0.234564 0.517098 0.393534 [1000 rows x 3 columns]
Es posible utilizar resample(), expanding() y rolling() como métodos en groupbys.
resample()
expanding()
rolling()
El siguiente ejemplo aplicará el rolling() método en las muestras de la columna B basado en los grupos de la columna A.
In [129]: df_re = pd.DataFrame({"A": [1] * 10 + [5] * 10, "B": np.arange(20)}) In [130]: df_re Out[130]: A B 0 1 0 1 1 1 2 1 2 3 1 3 4 1 4 .. .. .. 15 5 15 16 5 16 17 5 17 18 5 18 19 5 19 [20 rows x 2 columns] In [131]: df_re.groupby("A").rolling(4).B.mean() Out[131]: A 1 0 NaN 1 NaN 2 NaN 3 1.5 4 2.5 ... 5 15 13.5 16 14.5 17 15.5 18 16.5 19 17.5 Name: B, Length: 20, dtype: float64
los expanding() El método acumulará una operación determinada (sum() en el ejemplo) para todos los miembros de cada grupo en particular.
In [132]: df_re.groupby("A").expanding().sum() Out[132]: A B A 1 0 1.0 0.0 1 2.0 1.0 2 3.0 3.0 3 4.0 6.0 4 5.0 10.0 ... ... ... 5 15 30.0 75.0 16 35.0 91.0 17 40.0 108.0 18 45.0 126.0 19 50.0 145.0 [20 rows x 2 columns]
Suponga que desea utilizar el resample() método para obtener una frecuencia diaria en cada grupo de su marco de datos y desea completar los valores faltantes con el ffill() método.
ffill()
In [133]: df_re = pd.DataFrame( .....: { .....: "date": pd.date_range(start="2016-01-01", periods=4, freq="W"), .....: "group": [1, 1, 2, 2], .....: "val": [5, 6, 7, 8], .....: } .....: ).set_index("date") .....: In [134]: df_re Out[134]: group val date 2016-01-03 1 5 2016-01-10 1 6 2016-01-17 2 7 2016-01-24 2 8 In [135]: df_re.groupby("group").resample("1D").ffill() Out[135]: group val group date 1 2016-01-03 1 5 2016-01-04 1 5 2016-01-05 1 5 2016-01-06 1 5 2016-01-07 1 5 ... ... ... 2 2016-01-20 2 7 2016-01-21 2 7 2016-01-22 2 7 2016-01-23 2 7 2016-01-24 2 8 [16 rows x 2 columns]
los filter El método devuelve un subconjunto del objeto original. Supongamos que queremos tomar solo elementos que pertenecen a grupos con una suma de grupo mayor que 2.
filter
In [136]: sf = pd.Series([1, 1, 2, 3, 3, 3]) In [137]: sf.groupby(sf).filter(lambda x: x.sum() > 2) Out[137]: 3 3 4 3 5 3 dtype: int64
El argumento de filter debe ser una función que, aplicada al grupo en su conjunto, devuelva True o False.
Otra operación útil es filtrar los elementos que pertenecen a grupos con solo un par de miembros.
In [138]: dff = pd.DataFrame({"A": np.arange(8), "B": list("aabbbbcc")}) In [139]: dff.groupby("B").filter(lambda x: len(x) > 2) Out[139]: A B 2 2 b 3 3 b 4 4 b 5 5 b
Alternativamente, en lugar de eliminar los grupos infractores, podemos devolver objetos indexados como similares donde los grupos que no pasan el filtro se llenan con NaN.
In [140]: dff.groupby("B").filter(lambda x: len(x) > 2, dropna=False) Out[140]: A B 0 NaN NaN 1 NaN NaN 2 2.0 b 3 3.0 b 4 4.0 b 5 5.0 b 6 NaN NaN 7 NaN NaN
Para DataFrames con múltiples columnas, los filtros deben especificar explícitamente una columna como criterio de filtro.
In [141]: dff["C"] = np.arange(8) In [142]: dff.groupby("B").filter(lambda x: len(x["C"]) > 2) Out[142]: A B C 2 2 b 2 3 3 b 3 4 4 b 4 5 5 b 5
Algunas funciones cuando se aplican a un objeto groupby actuarán como un filtrar en la entrada, devolviendo una forma reducida del original (y potencialmente eliminando grupos), pero con el índice sin cambios. Paso as_index=False no afectará a estos métodos de transformación.
Por ejemplo: head, tail.
head, tail
In [143]: dff.groupby("B").head(2) Out[143]: A B C 0 0 a 0 1 1 a 1 2 2 b 2 3 3 b 3 6 6 c 6 7 7 c 7
Al realizar una agregación o transformación, es posible que desee llamar a un método de instancia en cada grupo de datos. Esto es bastante fácil de hacer pasando funciones lambda:
In [144]: grouped = df.groupby("A") In [145]: grouped.agg(lambda x: x.std()) Out[145]: C D A bar 0.181231 1.366330 foo 0.912265 0.884785
Pero es bastante detallado y puede ser desordenado si necesita pasar argumentos adicionales. Con un poco de inteligencia en la metaprogramación, GroupBy ahora tiene la capacidad de “enviar” llamadas a métodos a los grupos:
In [146]: grouped.std() Out[146]: C D A bar 0.181231 1.366330 foo 0.912265 0.884785
Lo que realmente está sucediendo aquí es que se está generando un contenedor de función. Cuando se invoca, toma cualquier argumento pasado e invoca la función con cualquier argumento en cada grupo (en el ejemplo anterior, el std función). Los resultados luego se combinan juntos en el estilo de agg y transform (en realidad usa apply inferir el encolado, documentado a continuación). Esto permite que algunas operaciones se lleven a cabo de forma bastante sucinta:
agg
apply
In [147]: tsdf = pd.DataFrame( .....: np.random.randn(1000, 3), .....: index=pd.date_range("1/1/2000", periods=1000), .....: columns=["A", "B", "C"], .....: ) .....: In [148]: tsdf.iloc[::2] = np.nan In [149]: grouped = tsdf.groupby(lambda x: x.year) In [150]: grouped.fillna(method="pad") Out[150]: A B C 2000-01-01 NaN NaN NaN 2000-01-02 -0.353501 -0.080957 -0.876864 2000-01-03 -0.353501 -0.080957 -0.876864 2000-01-04 0.050976 0.044273 -0.559849 2000-01-05 0.050976 0.044273 -0.559849 ... ... ... ... 2002-09-22 0.005011 0.053897 -1.026922 2002-09-23 0.005011 0.053897 -1.026922 2002-09-24 -0.456542 -1.849051 1.559856 2002-09-25 -0.456542 -1.849051 1.559856 2002-09-26 1.123162 0.354660 1.128135 [1000 rows x 3 columns]
En este ejemplo, dividimos la colección de series de tiempo en fragmentos anuales y luego los llamamos de forma independiente Fillna en los grupos.
los nlargest y nsmallest los métodos funcionan en Series groupbys de estilo:
nlargest
nsmallest
In [151]: s = pd.Series([9, 8, 7, 5, 19, 1, 4.2, 3.3]) In [152]: g = pd.Series(list("abababab")) In [153]: gb = s.groupby(g) In [154]: gb.nlargest(3) Out[154]: a 4 19.0 0 9.0 2 7.0 b 1 8.0 3 5.0 7 3.3 dtype: float64 In [155]: gb.nsmallest(3) Out[155]: a 6 4.2 2 7.0 0 9.0 b 5 1.0 7 3.3 3 5.0 dtype: float64
Es posible que algunas operaciones en los datos agrupados no encajen en las categorías agregadas o transformadas. O puede que simplemente desee que GroupBy infiera cómo combinar los resultados. Para estos, use el apply función, que puede ser sustituida por ambas aggregate y transform en muchos casos de uso estándar. Sin embargo, apply puede manejar algunos casos de uso excepcionales, por ejemplo:
In [156]: df Out[156]: A B C D 0 foo one -0.575247 1.346061 1 bar one 0.254161 1.511763 2 foo two -1.143704 1.627081 3 bar three 0.215897 -0.990582 4 foo two 1.193555 -0.441652 5 bar two -0.077118 1.211526 6 foo one -0.408530 0.268520 7 foo three -0.862495 0.024580 In [157]: grouped = df.groupby("A") # could also just call .describe() In [158]: grouped["C"].apply(lambda x: x.describe()) Out[158]: A bar count 3.000000 mean 0.130980 std 0.181231 min -0.077118 25% 0.069390 ... foo min -1.143704 25% -0.862495 50% -0.575247 75% -0.408530 max 1.193555 Name: C, Length: 16, dtype: float64
La dimensión del resultado devuelto también puede cambiar:
In [159]: grouped = df.groupby('A')['C'] In [160]: def f(group): .....: return pd.DataFrame({'original': group, .....: 'demeaned': group - group.mean()}) .....: In [161]: grouped.apply(f) Out[161]: original demeaned 0 -0.575247 -0.215962 1 0.254161 0.123181 2 -1.143704 -0.784420 3 0.215897 0.084917 4 1.193555 1.552839 5 -0.077118 -0.208098 6 -0.408530 -0.049245 7 -0.862495 -0.503211
apply on a Series puede operar en un valor devuelto de la función aplicada, que es en sí misma una serie, y posiblemente upcast el resultado a un DataFrame:
In [162]: def f(x): .....: return pd.Series([x, x ** 2], index=["x", "x^2"]) .....: In [163]: s = pd.Series(np.random.rand(5)) In [164]: s Out[164]: 0 0.321438 1 0.493496 2 0.139505 3 0.910103 4 0.194158 dtype: float64 In [165]: s.apply(f) Out[165]: x x^2 0 0.321438 0.103323 1 0.493496 0.243538 2 0.139505 0.019462 3 0.910103 0.828287 4 0.194158 0.037697
apply Puede actuar como reductor, transformador, o función de filtro, dependiendo exactamente de lo que se le pasa. Entonces, dependiendo del camino tomado y exactamente lo que esté agrupando. Por lo tanto, las columnas agrupadas pueden incluirse en la salida, así como establecer los índices.
Nuevo en la versión 1.1.
Si Numba se instala como una dependencia opcional, el transform y aggregate soporte de métodos engine="numba" y engine_kwargs argumentos. los engine_kwargs argumento es un diccionario de argumentos de palabras clave que se pasarán al decorador numba.jit. Estos argumentos de palabras clave se aplicarán a la función pasada. Actualmente solo nogil, nopython, y parallel son compatibles, y sus valores predeterminados se establecen en False, True y False respectivamente.
engine="numba"
engine_kwargs
nogil
nopython
parallel
La firma de la función debe comenzar con values, index exactamente ya que los datos pertenecientes a cada grupo se pasarán a values, y el índice de grupo se pasará a index.
values, index
values
index
Advertencia
Cuando usas engine="numba", no habrá comportamiento de “retroceso” internamente. Los datos del grupo y el índice del grupo se pasarán como matrices NumPy a la función definida por el usuario JITed, y no se intentarán intentos de ejecución alternativos.
En términos de rendimiento, la primera vez que se ejecuta una función con el motor Numba será lenta ya que Numba tendrá algunos gastos generales de compilación de funciones. Sin embargo, las funciones compiladas se almacenan en caché y las llamadas posteriores serán rápidas. En general, el motor Numba funciona con una mayor cantidad de puntos de datos (por ejemplo, más de 1 millón).
In [1]: N = 10 ** 3 In [2]: data = {0: [str(i) for i in range(100)] * N, 1: list(range(100)) * N} In [3]: df = pd.DataFrame(data, columns=[0, 1]) In [4]: def f_numba(values, index): ...: total = 0 ...: for i, value in enumerate(values): ...: if i % 2: ...: total += value + 5 ...: else: ...: total += value * 2 ...: return total ...: In [5]: def f_cython(values): ...: total = 0 ...: for i, value in enumerate(values): ...: if i % 2: ...: total += value + 5 ...: else: ...: total += value * 2 ...: return total ...: In [6]: groupby = df.groupby(0) # Run the first time, compilation time will affect performance In [7]: %timeit -r 1 -n 1 groupby.aggregate(f_numba, engine='numba') # noqa: E225 2.14 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each) # Function is cached and performance will improve In [8]: %timeit groupby.aggregate(f_numba, engine='numba') 4.93 ms ± 32.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) In [9]: %timeit groupby.aggregate(f_cython, engine='cython') 18.6 ms ± 84.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
De nuevo, considere el DataFrame de ejemplo que hemos estado viendo:
In [166]: df Out[166]: A B C D 0 foo one -0.575247 1.346061 1 bar one 0.254161 1.511763 2 foo two -1.143704 1.627081 3 bar three 0.215897 -0.990582 4 foo two 1.193555 -0.441652 5 bar two -0.077118 1.211526 6 foo one -0.408530 0.268520 7 foo three -0.862495 0.024580
Suponga que deseamos calcular la desviación estándar agrupada por A columna. Hay un pequeño problema, es decir, que no nos importan los datos de la columna. B. Nos referimos a esto como una columna de “molestias”. Si la función de agregación pasada no se puede aplicar a algunas columnas, las columnas problemáticas se descartarán (silenciosamente). Por tanto, esto no plantea ningún problema:
In [167]: df.groupby("A").std() Out[167]: C D A bar 0.181231 1.366330 foo 0.912265 0.884785
Tenga en cuenta que df.groupby('A').colname.std(). es más eficiente que df.groupby('A').std().colname, por lo que si el resultado de una función de agregación solo es interesante en una columna (aquí colname), se puede filtrar antes de aplicando la función de agregación.
df.groupby('A').colname.std().
df.groupby('A').std().colname
colname
Cualquier columna de objeto, también si contiene valores numéricos como Decimal objetos, se considera como una columna “molestia”. Se excluyen de las funciones agregadas automáticamente en groupby.
Decimal
Si desea incluir columnas decimales o de objeto en una agregación con otros tipos de datos no molestos, debe hacerlo explícitamente.
In [168]: from decimal import Decimal In [169]: df_dec = pd.DataFrame( .....: { .....: "id": [1, 2, 1, 2], .....: "int_column": [1, 2, 3, 4], .....: "dec_column": [ .....: Decimal("0.50"), .....: Decimal("0.15"), .....: Decimal("0.25"), .....: Decimal("0.40"), .....: ], .....: } .....: ) .....: # Decimal columns can be sum'd explicitly by themselves... In [170]: df_dec.groupby(["id"])[["dec_column"]].sum() Out[170]: dec_column id 1 0.75 2 0.55 # ...but cannot be combined with standard data types or they will be excluded In [171]: df_dec.groupby(["id"])[["int_column", "dec_column"]].sum() Out[171]: int_column id 1 4 2 6 # Use .agg function to aggregate over standard and "nuisance" data types # at the same time In [172]: df_dec.groupby(["id"]).agg({"int_column": "sum", "dec_column": "sum"}) Out[172]: int_column dec_column id 1 4 0.75 2 6 0.55
Cuando se usa un Categorical mero (como un solo mero, o como parte de varios mero), el observed La palabra clave controla si se devuelve un producto cartesiano de todos los posibles valores de agrupadores (observed=False) o solo aquellos que sean meros observados (observed=True).
Categorical
observed
observed=False
observed=True
Mostrar todos los valores:
In [173]: pd.Series([1, 1, 1]).groupby( .....: pd.Categorical(["a", "a", "a"], categories=["a", "b"]), observed=False .....: ).count() .....: Out[173]: a 3 b 0 dtype: int64
Muestre solo los valores observados:
In [174]: pd.Series([1, 1, 1]).groupby( .....: pd.Categorical(["a", "a", "a"], categories=["a", "b"]), observed=True .....: ).count() .....: Out[174]: a 3 dtype: int64
El dtype devuelto de la voluntad agrupada siempre incluir todos de las categorías que se agruparon.
In [175]: s = ( .....: pd.Series([1, 1, 1]) .....: .groupby(pd.Categorical(["a", "a", "a"], categories=["a", "b"]), observed=False) .....: .count() .....: ) .....: In [176]: s.index.dtype Out[176]: CategoricalDtype(categories=['a', 'b'], ordered=False)
Si hay valores de NaN o NaT en la clave de agrupación, estos se excluirán automáticamente. En otras palabras, nunca habrá un “grupo NA” o un “grupo NaT”. Este no era el caso en versiones anteriores de pandas, pero los usuarios generalmente descartaban al grupo NA de todos modos (y apoyarlo era un dolor de cabeza de implementación).
Variables categóricas representadas como instancia de pandas Categorical La clase se puede utilizar como claves de grupo. Si es así, se conservará el orden de los niveles:
In [177]: data = pd.Series(np.random.randn(100)) In [178]: factor = pd.qcut(data, [0, 0.25, 0.5, 0.75, 1.0]) In [179]: data.groupby(factor).mean() Out[179]: (-2.645, -0.523] -1.362896 (-0.523, 0.0296] -0.260266 (0.0296, 0.654] 0.361802 (0.654, 2.21] 1.073801 dtype: float64
Es posible que deba especificar un poco más de datos para agrupar correctamente. Puedes usar el pd.Grouper para proporcionar este control local.
In [180]: import datetime In [181]: df = pd.DataFrame( .....: { .....: "Branch": "A A A A A A A B".split(), .....: "Buyer": "Carl Mark Carl Carl Joe Joe Joe Carl".split(), .....: "Quantity": [1, 3, 5, 1, 8, 1, 9, 3], .....: "Date": [ .....: datetime.datetime(2013, 1, 1, 13, 0), .....: datetime.datetime(2013, 1, 1, 13, 5), .....: datetime.datetime(2013, 10, 1, 20, 0), .....: datetime.datetime(2013, 10, 2, 10, 0), .....: datetime.datetime(2013, 10, 1, 20, 0), .....: datetime.datetime(2013, 10, 2, 10, 0), .....: datetime.datetime(2013, 12, 2, 12, 0), .....: datetime.datetime(2013, 12, 2, 14, 0), .....: ], .....: } .....: ) .....: In [182]: df Out[182]: Branch Buyer Quantity Date 0 A Carl 1 2013-01-01 13:00:00 1 A Mark 3 2013-01-01 13:05:00 2 A Carl 5 2013-10-01 20:00:00 3 A Carl 1 2013-10-02 10:00:00 4 A Joe 8 2013-10-01 20:00:00 5 A Joe 1 2013-10-02 10:00:00 6 A Joe 9 2013-12-02 12:00:00 7 B Carl 3 2013-12-02 14:00:00
Agrupar por una columna específica con la frecuencia deseada. Esto es como un remuestreo.
In [183]: df.groupby([pd.Grouper(freq="1M", key="Date"), "Buyer"]).sum() Out[183]: Quantity Date Buyer 2013-01-31 Carl 1 Mark 3 2013-10-31 Carl 6 Joe 9 2013-12-31 Carl 3 Joe 9
Tiene una especificación ambigua en el sentido de que tiene un índice con nombre y una columna que podrían ser agrupadores potenciales.
In [184]: df = df.set_index("Date") In [185]: df["Date"] = df.index + pd.offsets.MonthEnd(2) In [186]: df.groupby([pd.Grouper(freq="6M", key="Date"), "Buyer"]).sum() Out[186]: Quantity Date Buyer 2013-02-28 Carl 1 Mark 3 2014-02-28 Carl 9 Joe 18 In [187]: df.groupby([pd.Grouper(freq="6M", level="Date"), "Buyer"]).sum() Out[187]: Quantity Date Buyer 2013-01-31 Carl 1 Mark 3 2014-01-31 Carl 9 Joe 18
Al igual que para un DataFrame o Series, puede llamar de cabeza y cola a un grupo por:
In [188]: df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=["A", "B"]) In [189]: df Out[189]: A B 0 1 2 1 1 4 2 5 6 In [190]: g = df.groupby("A") In [191]: g.head(1) Out[191]: A B 0 1 2 2 5 6 In [192]: g.tail(1) Out[192]: A B 1 1 4 2 5 6
Esto muestra la primera o la última n filas de cada grupo.
Para seleccionar de un DataFrame o Series el enésimo elemento, use nth(). Este es un método de reducción y devolverá una sola fila (o ninguna fila) por grupo si pasa un int para n:
In [193]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=["A", "B"]) In [194]: g = df.groupby("A") In [195]: g.nth(0) Out[195]: B A 1 NaN 5 6.0 In [196]: g.nth(-1) Out[196]: B A 1 4.0 5 6.0 In [197]: g.nth(1) Out[197]: B A 1 4.0
Si desea seleccionar el n-ésimo elemento no nulo, utilice el dropna kwarg. Para un DataFrame, esto debería ser 'any' o 'all' al igual que pasarías a dropna:
'any'
'all'
# nth(0) is the same as g.first() In [198]: g.nth(0, dropna="any") Out[198]: B A 1 4.0 5 6.0 In [199]: g.first() Out[199]: B A 1 4.0 5 6.0 # nth(-1) is the same as g.last() In [200]: g.nth(-1, dropna="any") # NaNs denote group exhausted when using dropna Out[200]: B A 1 4.0 5 6.0 In [201]: g.last() Out[201]: B A 1 4.0 5 6.0 In [202]: g.B.nth(0, dropna="all") Out[202]: A 1 4.0 5 6.0 Name: B, dtype: float64
Al igual que con otros métodos, pasar as_index=False, logrará una filtración, que devuelve la fila agrupada.
In [203]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=["A", "B"]) In [204]: g = df.groupby("A", as_index=False) In [205]: g.nth(0) Out[205]: A B 0 1 NaN 2 5 6.0 In [206]: g.nth(-1) Out[206]: A B 1 1 4.0 2 5 6.0
También puede seleccionar varias filas de cada grupo especificando varios valores n-ésimo como una lista de entradas.
In [207]: business_dates = pd.date_range(start="4/1/2014", end="6/30/2014", freq="B") In [208]: df = pd.DataFrame(1, index=business_dates, columns=["a", "b"]) # get the first, 4th, and last date index for each month In [209]: df.groupby([df.index.year, df.index.month]).nth([0, 3, -1]) Out[209]: a b 2014 4 1 1 4 1 1 4 1 1 5 1 1 5 1 1 5 1 1 6 1 1 6 1 1 6 1 1
Para ver el orden en el que aparece cada fila dentro de su grupo, use el cumcount método:
cumcount
In [210]: dfg = pd.DataFrame(list("aaabba"), columns=["A"]) In [211]: dfg Out[211]: A 0 a 1 a 2 a 3 b 4 b 5 a In [212]: dfg.groupby("A").cumcount() Out[212]: 0 0 1 1 2 2 3 0 4 1 5 3 dtype: int64 In [213]: dfg.groupby("A").cumcount(ascending=False) Out[213]: 0 3 1 2 2 1 3 1 4 0 5 0 dtype: int64
Para ver el orden de los grupos (a diferencia del orden de las filas dentro de un grupo dado por cumcount) puedes usar ngroup().
ngroup()
Tenga en cuenta que los números dados a los grupos coinciden con el orden en el que se verían los grupos al iterar sobre el objeto groupby, no con el orden en que se observaron por primera vez.
In [214]: dfg = pd.DataFrame(list("aaabba"), columns=["A"]) In [215]: dfg Out[215]: A 0 a 1 a 2 a 3 b 4 b 5 a In [216]: dfg.groupby("A").ngroup() Out[216]: 0 0 1 0 2 0 3 1 4 1 5 0 dtype: int64 In [217]: dfg.groupby("A").ngroup(ascending=False) Out[217]: 0 1 1 1 2 1 3 0 4 0 5 1 dtype: int64
Groupby también trabaja con algunos métodos de trazado. Por ejemplo, supongamos que sospechamos que algunas características en un DataFrame pueden diferir por grupo, en este caso, los valores en la columna 1 donde el grupo es “B” son 3 más altos en promedio.
In [218]: np.random.seed(1234) In [219]: df = pd.DataFrame(np.random.randn(50, 2)) In [220]: df["g"] = np.random.choice(["A", "B"], size=50) In [221]: df.loc[df["g"] == "B", 1] += 3
Podemos visualizar esto fácilmente con un diagrama de caja:
In [222]: df.groupby("g").boxplot() Out[222]: A AxesSubplot(0.1,0.15;0.363636x0.75) B AxesSubplot(0.536364,0.15;0.363636x0.75) dtype: object
El resultado de llamar boxplot es un diccionario cuyas claves son los valores de nuestra columna de agrupación g (“A y B”). Los valores del diccionario resultante pueden ser controlados por el return_type palabra clave de boxplot. Ver el documentación de visualización para más.
boxplot
g
return_type
Por razones históricas, df.groupby("g").boxplot() no es equivalente a df.boxplot(by="g"). Ver aquí para una explicación.
df.groupby("g").boxplot()
df.boxplot(by="g")
Similar a la funcionalidad proporcionada por DataFrame y Series, funciones que toman GroupBy los objetos se pueden encadenar entre sí utilizando un pipe método para permitir una sintaxis más limpia y legible. Para leer sobre .pipe en términos generales, ver aquí.
pipe
.pipe
Combinatorio .groupby y .pipe a menudo es útil cuando necesita reutilizar objetos GroupBy.
.groupby
Como ejemplo, imagine tener un DataFrame con columnas para tiendas, productos, ingresos y cantidad vendida. Nos gustaría hacer un cálculo grupal de precios (es decir, ingresos / cantidad) por tienda y por producto. Podríamos hacer esto en una operación de varios pasos, pero expresarlo en términos de canalización puede hacer que el código sea más legible. Primero configuramos los datos:
In [223]: n = 1000 In [224]: df = pd.DataFrame( .....: { .....: "Store": np.random.choice(["Store_1", "Store_2"], n), .....: "Product": np.random.choice(["Product_1", "Product_2"], n), .....: "Revenue": (np.random.random(n) * 50 + 10).round(2), .....: "Quantity": np.random.randint(1, 10, size=n), .....: } .....: ) .....: In [225]: df.head(2) Out[225]: Store Product Revenue Quantity 0 Store_2 Product_1 26.12 1 1 Store_2 Product_1 28.86 1
Ahora, para encontrar precios por tienda / producto, simplemente podemos hacer:
In [226]: ( .....: df.groupby(["Store", "Product"]) .....: .pipe(lambda grp: grp.Revenue.sum() / grp.Quantity.sum()) .....: .unstack() .....: .round(2) .....: ) .....: Out[226]: Product Product_1 Product_2 Store Store_1 6.82 7.05 Store_2 6.30 6.64
La tubería también puede ser expresiva cuando desea entregar un objeto agrupado a alguna función arbitraria, por ejemplo:
In [227]: def mean(groupby): .....: return groupby.mean() .....: In [228]: df.groupby(["Store", "Product"]).pipe(mean) Out[228]: Revenue Quantity Store Product Store_1 Product_1 34.622727 5.075758 Product_2 35.482815 5.029630 Store_2 Product_1 32.972837 5.237589 Product_2 34.684360 5.224000
dónde mean toma un objeto GroupBy y encuentra la media de las columnas Ingresos y Cantidad respectivamente para cada combinación de Tienda-Producto. los mean función puede ser cualquier función que incluya un objeto GroupBy; los .pipe pasará el objeto GroupBy como parámetro a la función que especifique.
Reagrupe las columnas de un DataFrame de acuerdo con su suma y sume las agregadas.
In [229]: df = pd.DataFrame({"a": [1, 0, 0], "b": [0, 1, 0], "c": [1, 0, 0], "d": [2, 3, 4]}) In [230]: df Out[230]: a b c d 0 1 0 1 2 1 0 1 0 3 2 0 0 0 4 In [231]: df.groupby(df.sum(), axis=1).sum() Out[231]: 1 9 0 2 2 1 1 3 2 0 4
Mediante el uso ngroup(), podemos extraer información sobre los grupos de una manera similar a factorize() (como se describe más adelante en el reformando API) pero que se aplica naturalmente a múltiples columnas de tipo mixto y diferentes fuentes. Esto puede ser útil como un paso intermedio de tipo categórico en el procesamiento, cuando las relaciones entre las filas del grupo son más importantes que su contenido, o como entrada a un algoritmo que solo acepta la codificación entera. (Para obtener más información sobre la compatibilidad con pandas para obtener datos categóricos completos, consulte la Introducción categórica y el Documentación de la API.)
factorize()
In [232]: dfg = pd.DataFrame({"A": [1, 1, 2, 3, 2], "B": list("aaaba")}) In [233]: dfg Out[233]: A B 0 1 a 1 1 a 2 2 a 3 3 b 4 2 a In [234]: dfg.groupby(["A", "B"]).ngroup() Out[234]: 0 0 1 0 2 1 3 2 4 1 dtype: int64 In [235]: dfg.groupby(["A", [0, 0, 0, 1, 1]]).ngroup() Out[235]: 0 0 1 0 2 1 3 3 4 2 dtype: int64
El remuestreo produce nuevas muestras hipotéticas (remuestreos) a partir de datos observados ya existentes o de un modelo que genera datos. Estas nuevas muestras son similares a las muestras preexistentes.
Para volver a muestrear para trabajar en índices que no son de fecha y hora, se puede utilizar el siguiente procedimiento.
En los siguientes ejemplos, df.index // 5 devuelve una matriz binaria que se utiliza para determinar qué se selecciona para la operación groupby.
El siguiente ejemplo muestra cómo podemos reducir la resolución mediante la consolidación de muestras en menos muestras. Aquí usando df.index // 5, estamos agregando las muestras en contenedores. Aplicando std () función, agregamos la información contenida en muchas muestras en un pequeño subconjunto de valores que es su desviación estándar, reduciendo así el número de muestras.
In [236]: df = pd.DataFrame(np.random.randn(10, 2)) In [237]: df Out[237]: 0 1 0 -0.793893 0.321153 1 0.342250 1.618906 2 -0.975807 1.918201 3 -0.810847 -1.405919 4 -1.977759 0.461659 5 0.730057 -1.316938 6 -0.751328 0.528290 7 -0.257759 -1.081009 8 0.505895 -1.701948 9 -1.006349 0.020208 In [238]: df.index // 5 Out[238]: Int64Index([0, 0, 0, 0, 0, 1, 1, 1, 1, 1], dtype='int64') In [239]: df.groupby(df.index // 5).std() Out[239]: 0 1 0 0.823647 1.312912 1 0.760109 0.942941
Agrupe columnas de DataFrame, calcule un conjunto de métricas y devuelva una serie con nombre. El nombre de la serie se utiliza como nombre del índice de columna. Esto es especialmente útil junto con operaciones de remodelación como apilamiento en las que el nombre del índice de la columna se utilizará como el nombre de la columna insertada:
In [240]: df = pd.DataFrame( .....: { .....: "a": [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2], .....: "b": [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1], .....: "c": [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0], .....: "d": [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1], .....: } .....: ) .....: In [241]: def compute_metrics(x): .....: result = {"b_sum": x["b"].sum(), "c_mean": x["c"].mean()} .....: return pd.Series(result, name="metrics") .....: In [242]: result = df.groupby("a").apply(compute_metrics) In [243]: result Out[243]: metrics b_sum c_mean a 0 2.0 0.5 1 2.0 0.5 2 2.0 0.5 In [244]: result.stack() Out[244]: a metrics 0 b_sum 2.0 c_mean 0.5 1 b_sum 2.0 c_mean 0.5 2 b_sum 2.0 c_mean 0.5 dtype: float64
Herramientas computacionales Operaciones de ventanas