girona español descargar bonn python pandas numpy dataframe typechecking

python - español - qgis girona



¿Existe un método eficiente para verificar si una columna tiene dtypes mixtos? (3)

Considerar

np.random.seed(0) s1 = pd.Series([1, 2, ''a'', ''b'', [1, 2, 3]]) s2 = np.random.randn(len(s1)) s3 = np.random.choice(list(''abcd''), len(s1)) df = pd.DataFrame({''A'': s1, ''B'': s2, ''C'': s3}) df A B C 0 1 1.764052 a 1 2 0.400157 d 2 a 0.978738 c 3 b 2.240893 a 4 [1, 2, 3] 1.867558 a

La columna "A" tiene tipos de datos mixtos. Me gustaría encontrar una manera realmente rápida de determinar esto. No sería tan simple como verificar si el type == object , ya que identificaría "C" como un falso positivo.

Puedo pensar en hacer esto con

df.applymap(type).nunique() > 1 A True B False C False dtype: bool

Pero el type llamada atop applymap es bastante lento. Especialmente para marcos más grandes.

%timeit df.applymap(type).nunique() > 1 3.95 ms ± 88 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

¿Podemos hacerlo mejor (quizás con NumPy)? Puedo aceptar "No" si su argumento es suficientemente convincente. :-)


En pandas hay infer_dtype() que podría ser útil aquí.

Escrito en Cython ( enlace de código ), devuelve una cadena que resume los valores en el objeto pasado. Se usa mucho en las partes internas de los pandas, por lo que podemos esperar razonablemente que se haya diseñado teniendo en cuenta la eficiencia.

>>> from pandas.api.types import infer_dtype

Ahora, la columna A es una mezcla de enteros y algunos otros tipos:

>>> infer_dtype(df.A) ''mixed-integer''

Los valores de la columna B son todos de tipo flotante:

>>> infer_dtype(df.B) ''floating''

La columna C contiene cadenas:

>>> infer_dtype(df.B) ''string''

El tipo general "catchall" para valores mixtos es simplemente "mixto":

>>> infer_dtype([''a string'', pd.Timedelta(10)]) ''mixed''

Una mezcla de flotadores y enteros es '''' mezclado-entero-flotante '''':

>>> infer_dtype([3.141, 99]) ''mixed-integer-float''

Para realizar la función que describe en su pregunta, un enfoque podría ser crear una función que detecte los casos mixtos relevantes:

def is_mixed(col): return infer_dtype(col) in [''mixed'', ''mixed-integer'']

Entonces tiene:

>>> df.apply(is_mixed) A True B False C False dtype: bool


Este es un enfoque que utiliza el hecho de que en Python3 no se pueden comparar diferentes tipos. La idea es ejecutar max sobre la matriz, que al ser un componente debe ser razonablemente rápido. Y lo hace corto-cicuit.

def ismixed(a): try: max(a) return False except TypeError as e: # we take this to imply mixed type msg, fst, and_, snd = str(e).rsplit('' '', 3) assert msg=="''>'' not supported between instances of" assert and_=="and" assert fst!=snd return True except ValueError as e: # catch empty arrays assert str(e)=="max() arg is an empty sequence" return False

Sin embargo, no captura tipos numéricos mixtos. Además, los objetos que simplemente no admiten la comparación pueden disparar esto.

Pero es razonablemente rápido. Si despojamos a todos los pandas arriba:

v = df.values list(map(is_mixed, v.T)) # [True, False, False] timeit(lambda: list(map(ismixed, v.T)), number=1000) # 0.008936170022934675

Para comparacion

timeit(lambda: list(map(infer_dtype, v.T)), number=1000) # 0.02499613002873957


No está seguro de cómo necesita el resultado, pero puede map el type a df.values.ravel() y crear un diccionario del nombre del enlace de la columna a la comparación del len de un set superior a 1 para cada segmento del l como:

l = list(map(type, df.values.ravel())) print ({df.columns[i]:len(set(l[i::df.shape[1]])) > 1 for i in range(df.shape[1])}) {''A'': True, ''B'': False, ''C'': False}

Sincronización:

%timeit df.applymap(type).nunique() > 1 #3.25 ms ± 516 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %%timeit l = list(map(type, df.values.ravel())) {df.columns[i]:len(set(l[i::df.shape[1]])) > 1 for i in range(df.shape[1])} #100 µs ± 5.08 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

EDITAR para un marco de datos más grande, la mejora en el tiempo es menos interesante, sin embargo:

dfl = pd.concat([df]*100000,ignore_index=True) %timeit dfl.applymap(type).nunique() > 1 #519 ms ± 61.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) %%timeit l = list(map(type, dfl.values.ravel())) {dfl.columns[i]:len(set(l[i::dfl.shape[1]])) > 1 for i in range(dfl.shape[1])} #254 ms ± 33.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Una solución un poco más rápida en la misma idea:

%timeit { col: len(set(map(type, dfl[col])))>1 for col in dfl.columns} #124 ms ± 15.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)