Tengo un marco de datos Pandas que consta de varias columnas, cuyas celdas pueden o no contener una cadena. Como ejemplo:

import numpy as np
import pandas as pd

df = pd.DataFrame({'A':['asfe','eseg','eesg','4dsf','','hdt','gase','gex','gsges','hhbr'],
                   'B':['','bdb','htsdg','','rdshg','th','tjf','','',''],
                   'C':['hrd','jyf','sef','hdsr','','','','','hdts','aseg'],
                   'D':['','','hdts','afse','nfd','','htf','','',''],
                   'E':['','','','','jftd','','','','jfdt','']})

... que se parece a:

       A      B     C     D     E
0   asfe          hrd            
1   eseg    bdb   jyf            
2   eesg  htsdg   sef  hdts      
3   4dsf         hdsr  afse      
4         rdshg         nfd  jftd
5    hdt     th                  
6   gase    tjf         htf      
7    gex                         
8  gsges         hdts        jfdt
9   hhbr         aseg            

Quiero crear una columna que contenga una representación binaria de si la columna contiene una cadena o no; Como ejemplo, la primera fila se representaría como 10100.

La única forma en que puedo pensar en hacer esto es:

  1. Crear un marco de datos temporal
  2. Revise cada columna para detectar si las celdas contienen caracteres y se representan como 0/1
  3. Concatenar los resultados binarios en una sola cadena
  4. Copie la columna desde el marco de datos desde cero al original.

Este es el código que he creado:

scratchdf = pd.DataFrame().reindex_like(df)

for col in df.columns.values:
    scratchdf[col] = df[col].str.contains(r'\w+',regex = True).astype(int)

scratchdf['bin'] =  scratchdf['A'].astype(str) + \
                    scratchdf['B'].astype(str) + \
                    scratchdf['C'].astype(str) + \
                    scratchdf['D'].astype(str) + \
                    scratchdf['E'].astype(str)

df = df.join(scratchdf['bin'])

... que produce el marco de datos final:

       A      B     C     D     E    bin
0   asfe          hrd              10100
1   eseg    bdb   jyf              11100
2   eesg  htsdg   sef  hdts        11110
3   4dsf         hdsr  afse        10110
4         rdshg         nfd  jftd  01011
5    hdt     th                    11000
6   gase    tjf         htf        11010
7    gex                           10000
8  gsges         hdts        jfdt  10101
9   hhbr         aseg              10100

Esto funciona pero parece un poco derrochador (especialmente con grandes marcos de datos). ¿Hay alguna manera de crear la columna de representación binaria directamente, sin necesidad de crear un marco de datos temporal?

3
user1718097 27 feb. 2018 a las 10:16

3 respuestas

La mejor respuesta

Verifique la cadena vacía o conviértala a bool primero, luego conviértala a int, str y la última join o sum:

df['new'] = (df != '').astype(int).astype(str).apply(''.join, axis=1)

#faster alternative
df['new'] = (df != '').astype(int).astype(str).values.sum(axis=1)

print (df)

       A      B     C     D     E    new
0   asfe          hrd              10100
1   eseg    bdb   jyf              11100
2   eesg  htsdg   sef  hdts        11110
3   4dsf         hdsr  afse        10110
4         rdshg         nfd  jftd  01011
5    hdt     th                    11000
6   gase    tjf         htf        11010
7    gex                           10000
8  gsges         hdts        jfdt  10101
9   hhbr         aseg              10100

Tiempos :

df = pd.concat([df] * 1000, ignore_index=True)

In [99]: %timeit df.astype(bool).astype(int).astype(str).values.sum(axis=1)
10 loops, best of 3: 155 ms per loop

In [100]: %timeit (df != '').astype(int).astype(str).values.sum(axis=1)
10 loops, best of 3: 158 ms per loop

In [101]: %timeit (df != '').astype(int).astype(str).apply(''.join, axis=1)
1 loop, best of 3: 330 ms per loop

In [102]: %timeit df.astype(bool).astype(int).astype(str).apply(''.join, axis=1)
1 loop, best of 3: 326 ms per loop

In [103]: %timeit df.astype(bool).astype(int).apply(lambda row: ''.join(str(i) for i in row), axis=1)
1 loop, best of 3: 210 ms per loop
2
jezrael 27 feb. 2018 a las 07:38

Método 1

a = np.where(df != "", "1", "0").astype("|S1")
df["bin"] = np.apply_along_axis(lambda x: x.tostring().decode("utf-8"), 1, a)

Método 2

df["bin"] = np.append(
               np.where(df != "", "1", "0").astype("S1"), 
               np.array([["\n"]]).astype("S1").repeat(df.shape[0], axis=0), 
               axis=1
            ).tostring().decode("utf-8")[:-1].split("\n")

El método 2 agrega \n al final de la matriz numpy

array([[b'1', b'0', b'1', b'0', b'0', b'\n'],
       [b'1', b'1', b'1', b'0', b'0', b'\n'],
       [b'1', b'1', b'1', b'1', b'0', b'\n'],
       ...,
       [b'1', b'0', b'0', b'0', b'0', b'\n'],
       [b'1', b'0', b'1', b'0', b'1', b'\n'],
       [b'1', b'0', b'1', b'0', b'0', b'\n']], dtype='|S1')

Luego llame a tostring y decode. Elimine el último "\ n" y luego divídalo por "\ n".

Método 3 (Usando view Referencia: matriz numpy de caracteres para encadenar)

np.ascontiguousarray(
    np.where(df != "", "1", "0").astype("S1")
).view('|S5').astype(str)

Tiempos:

(Based on jezrael's setup df = pd.concat([df] * 1000, ignore_index=True))

# method 2
%timeit np.append(np.where(df != "", "1", "0").astype("S1"), np.array([["\n"]]).astype("S1").repeat(df.shape[0], axis=0), axis=1).tostring().decode("utf-8")[:-1].split("\n")
12.3 ms ± 175 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# method 3
%timeit np.ascontiguousarray(np.where(df != "", "1", "0").astype("S1")).view('|S5').astype(str)
12.8 ms ± 164 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# method 1 (slower)
%timeit np.apply_along_axis(lambda x: x.tostring().decode("utf-8"), 1, np.where(df != "", "1", "0").astype("S1"))
45 ms ± 1.86 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Experimentos replicados de jezrael.

In [99]: %timeit df.astype(bool).astype(int).astype(str).values.sum(axis=1)
28.9 ms ± 782 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [100]: %timeit (df != '').astype(int).astype(str).values.sum(axis=1)
29 ms ± 645 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [101]: %timeit (df != '').astype(int).astype(str).apply(''.join, axis=1)
168 ms ± 2.93 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [102]: %timeit df.astype(bool).astype(int).astype(str).apply(''.join, axis=1)
173 ms ± 7.36 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [103]: %timeit df.astype(bool).astype(int).apply(lambda row: ''.join(str(i) for i in row), axis=1)
159 ms ± 3.05 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
1
Tai 27 feb. 2018 a las 15:53

Puede usar el hecho de que las cadenas vacías corresponden a False y las no vacías a True. Por lo tanto, convertir el marco de datos de cadena en bool da un marco de datos como verdadero y falso. Reestructurar esto a int convierte verdadero a 1 y falso a 0, luego solo aplica una operación de unión entre filas:

df['binary'] = df.astype(bool).astype(int).apply(
    lambda row: ''.join(str(i) for i in row), axis=1)
print(df)

Resultado:

       A      B     C     D     E  binary
0   asfe          hrd              10100
1   eseg    bdb   jyf              11100
2   eesg  htsdg   sef  hdts        11110
3   4dsf         hdsr  afse        10110
4         rdshg         nfd  jftd  01011
5    hdt     th                    11000
6   gase    tjf         htf        11010
7    gex                           10000
8  gsges         hdts        jfdt  10101
9   hhbr         aseg              10100

Editar: acabo de darme cuenta de que otro usuario publicó básicamente lo mismo (también se corrigió el error de copia)

Aquí hay otra forma de usar generadores:

def iterable_to_binary_mask(iterable):
    bools = (bool(i) for i in iterable)
    ints = (int(i) for i in bools)
    strs = (str(i) for i in ints)
    return ''.join(strs)

df['binary'] = df.apply(iterable_to_binary_mask, axis=1)

Esto es aproximadamente 3 veces más lento que el enfoque de conversión de tipo en mi máquina, pero debería usar memoria mínima.

2
evamicur 27 feb. 2018 a las 07:47