Cuente las apariencias de un valor hasta que cambie a otro valor

Tengo el siguiente DataFrame:

df = pd.DataFrame([10, 10, 23, 23, 9, 9, 9, 10, 10, 10, 10, 12], columns=['values']) 

Quiero calcular la frecuencia de cada valor, pero no un recuento general, el recuento de cada valor hasta que cambie a otro valor.

Lo intenté:

 df['values'].value_counts() 

pero me da

 10 6 9 3 23 2 12 1 

La salida deseada es

 10:2 23:2 9:3 10:4 12:1 

¿Cómo puedo hacer esto?

Utilizar:

 df = df.groupby(df['values'].ne(df['values'].shift()).cumsum())['values'].value_counts() 

O:

 df = df.groupby([df['values'].ne(df['values'].shift()).cumsum(), 'values']).size() 

 print (df) values values 1 10 2 2 23 2 3 9 3 4 10 4 5 12 1 Name: values, dtype: int64 

Último para eliminar el primer nivel:

 df = df.reset_index(level=0, drop=True) print (df) values 10 2 23 2 9 3 10 4 12 1 dtype: int64 

Explicación :

Compare la columna original por shift ed con no igual a ne y luego agregue cumsum para la Series ayuda:

 print (pd.concat([df['values'], a, b, c], keys=('orig','shifted', 'not_equal', 'cumsum'), axis=1)) orig shifted not_equal cumsum 0 10 NaN True 1 1 10 10.0 False 1 2 23 10.0 True 2 3 23 23.0 False 2 4 9 23.0 True 3 5 9 9.0 False 3 6 9 9.0 False 3 7 10 9.0 True 4 8 10 10.0 False 4 9 10 10.0 False 4 10 10 10.0 False 4 11 12 10.0 True 5 

Puede realizar un seguimiento de dónde se producen los cambios en df['values'] :

 changes = df['values'].diff().ne(0).cumsum() print(changes) 0 1 1 1 2 2 3 2 4 3 5 3 6 3 7 4 8 4 9 4 10 4 11 5 

Y groupby los cambios y también df['values'] (para mantenerlos como índice) calculando el size de cada grupo

 df.groupby([changes,'values']).size().reset_index(level=0, drop=True) values 10 2 23 2 9 3 10 4 12 1 dtype: int64 

itertools.groupby

 from itertools import groupby pd.Series(*zip(*[[len([*v]), k] for k, v in groupby(df['values'])])) 10 2 23 2 9 3 10 4 12 1 dtype: int64 

Es un generador

 def f(x): count = 1 for this, that in zip(x, x[1:]): if this == that: count += 1 else: yield count, this count = 1 yield count, [*x][-1] pd.Series(*zip(*f(df['values']))) 10 2 23 2 9 3 10 4 12 1 dtype: int64 

Utilizando la crosstab

 df['key']=df['values'].diff().ne(0).cumsum() pd.crosstab(df['key'],df['values']) Out[353]: values 9 10 12 23 key 1 0 2 0 0 2 0 0 0 2 3 3 0 0 0 4 0 4 0 0 5 0 0 1 0 

Modificar ligeramente el resultado anterior.

 pd.crosstab(df['key'],df['values']).stack().loc[lambda x:x.ne(0)] Out[355]: key values 1 10 2 2 23 2 3 9 3 4 10 4 5 12 1 dtype: int64 

Base en python groupby

 from itertools import groupby [ (k,len(list(g))) for k,g in groupby(df['values'].tolist())] Out[366]: [(10, 2), (23, 2), (9, 3), (10, 4), (12, 1)] 

Esto está lejos del método más eficiente en tiempo / memoria que en este hilo, pero aquí hay un enfoque iterativo que es bastante sencillo. Por favor, siéntase alentado a sugerir mejoras en este método.

 import pandas as pd df = pd.DataFrame([10, 10, 23, 23, 9, 9, 9, 10, 10, 10, 10, 12], columns=['values']) dict_count = {} for v in df['values'].unique(): dict_count[v] = 0 curr_val = df.iloc[0]['values'] count = 1 for i in range(1, len(df)): if df.iloc[i]['values'] == curr_val: count += 1 else: if count > dict_count[curr_val]: dict_count[curr_val] = count curr_val = df.iloc[i]['values'] count = 1 if count > dict_count[curr_val]: dict_count[curr_val] = count df_count = pd.DataFrame(dict_count, index=[0]) print(df_count) 

La función groupby itertools puede ayudarlo, por str :

 >>> string = 'aabbaacc' >>> for char, freq in groupby('aabbaacc'): >>> print(char, len(list(freq)), sep=':', end='\n') [out]: a:2 b:2 a:2 c:2 

Esta función también funciona para la list :

 >>> df = pd.DataFrame([10, 10, 23, 23, 9, 9, 9, 10, 10, 10, 10, 12], columns=['values']) >>> for char, freq in groupby(df['values'].tolist()): >>> print(char, len(list(freq)), sep=':', end='\n') [out]: 10:2 23:2 9:3 10:4 12:1 

Note : para df , siempre usa esta forma como df [‘valores’] para tomar la columna ‘valores’, porque DataFrame tiene un values atributo