¿Cómo qcut con bordes de bin no únicos?

Mi pregunta es la misma que la anterior:

Binning con valores cero en pandas

sin embargo, todavía quiero incluir los valores 0 en un fractil. ¿Hay alguna forma de hacer esto? En otras palabras, si tengo 600 valores, el 50% de los cuales son 0, y el rest son, digamos, entre 1 y 100, ¿cómo categorizaría todos los valores 0 en fractil 1 y luego el rest de los valores distintos de cero? en las tags fractiles 2 a 10 (suponiendo que quiero 10 fractiles). ¿Puedo convertir los 0 a nan, q cortar los datos restantes de nan nanómetros en 9 fractiles (1 a 9), luego agregar 1 a cada etiqueta (ahora 2 a 10) y etiquetar todos los valores de 0 como fractil 1 manualmente? Incluso esto es complicado, porque en mi conjunto de datos, además de los 600 valores, también tengo otros doscientos que pueden ser nan antes de convertir los 0 a nan.

Actualización 1/26/14:

Se me ocurrió la siguiente solución provisional. Sin embargo, el problema con este código es que si el valor de alta frecuencia no está en los bordes de la distribución, inserta un contenedor adicional en el medio del conjunto existente de contenedores y desecha todo (o mucho).

def fractile_cut(ser, num_fractiles): num_valid = ser.valid().shape[0] remain_fractiles = num_fractiles vcounts = ser.value_counts() high_freq = [] i = 0 while vcounts.iloc[i] > num_valid/ float(remain_fractiles): curr_val = vcounts.index[i] high_freq.append(curr_val) remain_fractiles -= 1 num_valid = num_valid - vcounts[i] i += 1 curr_ser = ser.copy() curr_ser = curr_ser[~curr_ser.isin(high_freq)] qcut = pd.qcut(curr_ser, remain_fractiles, retbins=True) qcut_bins = qcut[1] all_bins = list(qcut_bins) for val in high_freq: bisect.insort(all_bins, val) cut = pd.cut(ser, bins=all_bins) ser_fractiles = pd.Series(cut.labels + 1, index=ser.index) return ser_fractiles 

El problema es que pandas.qcut elige los contenedores para que tenga el mismo número de registros en cada bin / quantile, pero el mismo valor no puede caer en múltiples bins / quantiles.

Las soluciones son :

1 – Use pandas> = 0.20.0 que tiene esta solución . Agregaron una opción duplicates='raise'|'drop' para controlar si se debe boost en bordes duplicados o soltarlos, lo que resultaría en menos contenedores de lo especificado, y algunos más grandes (con más elementos) que otros.

2 – Use pandas.cut que elige que las bandejas estén espaciadas uniformemente de acuerdo con los valores en sí, mientras que pandas.qcut elige las bandejas para que tenga el mismo número de registros en cada bandeja

3 – Disminuir el número de cuantiles . Menos cuantiles significa más elementos por cuantil

4 – Especifique un rango de cuantiles personalizados , por ejemplo, [0, .50, .75, 1.] para obtener un número desigual de elementos por cuantil

5 – Clasifique sus datos con DataFrame.rank (method = ‘first’). La clasificación asigna un valor único a cada elemento en el dataframe (la clasificación) mientras se mantiene el orden de los elementos (excepto los valores idénticos, que se clasificarán en el orden en que aparecen en la matriz, vea method = ‘first’). Esto soluciona el problema pero es posible que tenga valores idénticos (preclasificación) en diferentes cuantiles, que pueden ser correctos o no dependiendo de su intención.

Ejemplo:

 pd.qcut(df, nbins) <-- this generates "ValueError: Bin edges must be unique" 

Entonces usa esto en su lugar:

 pd.qcut(df.rank(method='first'), nbins) 

Otra forma de hacer esto es introducir una cantidad mínima de ruido, lo que creará de forma artificial bordes de contenedores únicos. Aquí hay un ejemplo:

 a = pd.Series(range(100) + ([0]*20)) def jitter(a_series, noise_reduction=1000000): return (np.random.random(len(a_series))*a_series.std()/noise_reduction)-(a_series.std()/(2*noise_reduction)) # and now this works by adding a little noise a_deciles = pd.qcut(a + jitter(a), 10, labels=False) 

Podemos recrear el error original usando algo como esto:

 a_deciles = pd.qcut(a, 10, labels=False) Traceback (most recent call last): File "", line 1, in  File "/usr/local/lib/python2.7/site-packages/pandas/tools/tile.py", line 173, in qcut precision=precision, include_lowest=True) File "/usr/local/lib/python2.7/site-packages/pandas/tools/tile.py", line 192, in _bins_to_cuts raise ValueError('Bin edges must be unique: %s' % repr(bins)) ValueError: Bin edges must be unique: array([ 0. , 0. , 0. , 3.8 , 11.73333333, 19.66666667, 27.6 , 35.53333333, 43.46666667, 51.4 , 59.33333333, 67.26666667, 75.2 , 83.13333333, 91.06666667, 99. ]) 

Usted pregunta acerca de la agrupación con bordes de bandeja no únicos, para los cuales tengo una respuesta bastante simple. En el caso de su ejemplo, su intención y el comportamiento de qcut divergen en la función pandas.tools.tile.qcut donde se definen los contenedores:

bins = algos.quantile(x, quantiles)

Lo cual, debido a que sus datos son 50% 0s, hace que las bandejas se devuelvan con múltiples bordes de bin en el valor 0 para cualquier valor de cuantiles mayor que 2. Veo dos soluciones posibles. En el primero, el espacio fractil se divide de manera uniforme, agrupando todos los 0s, pero no solo los 0s, en el primer bin. En el segundo, el espacio fractil se divide uniformemente para valores mayores que 0, agrupando todos los 0s y solo 0s en el primer bin.

 import numpy as np import pandas as pd import pandas.core.algorithms as algos from pandas import Series 

En ambos casos, crearé algunos datos de muestra aleatorios ajustando su descripción de 50% de ceros y los valores restantes entre 1 y 100

 zs = np.zeros(300) rs = np.random.randint(1, 100, size=300) arr=np.concatenate((zs, rs)) ser = Series(arr) 

Solución 1: el contenedor 1 contiene valores de 0 y valores bajos

 bins = algos.quantile(np.unique(ser), np.linspace(0, 1, 11)) result = pd.tools.tile._bins_to_cuts(ser, bins, include_lowest=True) 

El resultado es

 In[61]: result.value_counts() Out[61]: [0, 9.3] 323 (27.9, 38.2] 37 (9.3, 18.6] 37 (88.7, 99] 35 (57.8, 68.1] 32 (68.1, 78.4] 31 (78.4, 88.7] 30 (38.2, 48.5] 27 (48.5, 57.8] 26 (18.6, 27.9] 22 dtype: int64 

Solución 2: bin1 contiene solo 0s

 mx = np.ma.masked_equal(arr, 0, copy=True) bins = algos.quantile(arr[~mx.mask], np.linspace(0, 1, 11)) bins = np.insert(bins, 0, 0) bins[1] = bins[1]-(bins[1]/2) result = pd.tools.tile._bins_to_cuts(arr, bins, include_lowest=True) 

El resultado es:

 In[133]: result.value_counts() Out[133]: [0, 0.5] 300 (0.5, 11] 32 (11, 18.8] 28 (18.8, 29.7] 30 (29.7, 39] 35 (39, 50] 26 (50, 59] 31 (59, 71] 31 (71, 79.2] 27 (79.2, 90.2] 30 (90.2, 99] 30 dtype: int64 

Creo que se puede hacer un trabajo en la Solución 2 para hacerlo un poco más bonito, pero se puede ver que la matriz enmascarada es una herramienta útil para alcanzar sus objectives.

Si desea imponer contenedores de igual tamaño, incluso en presencia de valores duplicados, puede utilizar el siguiente proceso de 2 pasos:

  1. Clasifique sus valores, usando method = ‘first’ para que Python asigne un rango único a todos sus registros. Si hay un valor duplicado (es decir, un empate en la clasificación), este método elegirá el primer registro al que llegue y clasificará en ese orden.

df['rank'] = df['value'].rank(method='first')

  1. Use qcut en el rango para determinar cuantiles de igual tamaño. El siguiente ejemplo crea deciles (bins = 10).

df['decile'] = pd.qcut(df['rank'].values, 10).codes

También he tenido muchos problemas con qcut, así que usé la función Series.rank combinada con la creación de mis propios contenedores utilizando esos resultados. Mi código está en Github:

https://gist.github.com/ashishsingal1/e1828ffd1a449513b8f8