Pandas (IV)

Antes de nada, el contexto, para esta serie de entradas se va a usar lo siguiente:

Versión de Python:      3.3.1 (default, Apr 10 2013, 19:05:32) 
[GCC 4.6.3]
Versión de Pandas:      0.13.1
Versión de Numpy:       1.8.1
Versión de Matplotlib:  1.3.1

 

Y sin más preámbulos seguimos con esta cuarta parte de la serie.

Concatenando datos

Para concatenar ficheros se usa la función pd.concat (documentación oficial]. Un ejemplo rápido sería el siguiente:

datos1 = pd.DataFrame(np.random.randn(5,3))
datos2 = pd.DataFrame(np.random.randn(5,3))
piezas = [datos1, datos2]
datos_concatenados_a = pd.concat(piezas)
print('datos1n {}'.format(datos1))
print('datos2n {}'.format(datos2))
print('datos_concatenadosn {}'.format(datos_concatenados_a))

Cuyo resultado sería:

datos1
          0         1         2
0 -1.691985 -1.181241 -0.714437
1  0.955094 -0.238498  1.137918
2 -0.533739 -0.285976 -0.990184
3 -0.626446  0.664830  0.278803
4 -0.183818 -0.013190  0.505786

[5 rows x 3 columns]
datos2
          0         1         2
0 -2.063044  2.328388  0.043275
1 -1.720170 -0.039871  0.954244
2 -0.173751  0.047003 -0.979577
3 -0.293044  1.928332 -1.323554
4  0.705127  3.711652 -0.535096

[5 rows x 3 columns]
datos_concatenados
          0         1         2
0 -1.691985 -1.181241 -0.714437
1  0.955094 -0.238498  1.137918
2 -0.533739 -0.285976 -0.990184
3 -0.626446  0.664830  0.278803
4 -0.183818 -0.013190  0.505786
0 -2.063044  2.328388  0.043275
1 -1.720170 -0.039871  0.954244
2 -0.173751  0.047003 -0.979577
3 -0.293044  1.928332 -1.323554
4  0.705127  3.711652 -0.535096

[10 rows x 3 columns]

Interesante, rápido y limpio, como me gusta. Pero, si nos fijamos, tenemos un problema con los índices ya que algunos están repetidos. Si accedemos al índice 0, por ejemplo, obtendríamos dos filas de valores en lugar de una.

datos_concatenados_a.ix[0]
              0            1            2
0     -1.691985    -1.181241    -0.714437
0     -2.063044     2.328388     0.043275

2 rows × 3 columns

Lo anterior podría llevar a equívocos. Esto lo podemos solventar de varias formas. Una sería reescribiendo la columna de índices para que no haya malentendidos al hacer cualquier operación. Por ejemplo:

datos_concatenados_aa = datos_concatenados_a
datos_concatenados_aa.index = range(datos_concatenados_aa.shape[0])
print('datos_concatenadosn {}'.format(datos_concatenados_aa))
datos_concatenados
          0         1         2
0 -1.691985 -1.181241 -0.714437
1  0.955094 -0.238498  1.137918
2 -0.533739 -0.285976 -0.990184
3 -0.626446  0.664830  0.278803
4 -0.183818 -0.013190  0.505786
5 -2.063044  2.328388  0.043275
6 -1.720170 -0.039871  0.954244
7 -0.173751  0.047003 -0.979577
8 -0.293044  1.928332 -1.323554
9  0.705127  3.711652 -0.535096

[10 rows x 3 columns]

O usando la palabra clave ignore_index pasándole el valor True al crear la concatenación. Por ejemplo:

datos_concatenados_aa = pd.concat(piezas, ignore_index = True)
print(datos_concatenados_aa)
          0         1         2
0 -1.691985 -1.181241 -0.714437
1  0.955094 -0.238498  1.137918
2 -0.533739 -0.285976 -0.990184
3 -0.626446  0.664830  0.278803
4 -0.183818 -0.013190  0.505786
5 -2.063044  2.328388  0.043275
6 -1.720170 -0.039871  0.954244
7 -0.173751  0.047003 -0.979577
8 -0.293044  1.928332 -1.323554
9  0.705127  3.711652 -0.535096

[10 rows x 3 columns]

Vale, hemos solventado el anterior problema pero que pasa si, por la razón que sea, nos interesase conservar los índices originales. Podríamos usar palabras clave para cada 'cosa' concatenada en el DataFrame final. Ejemplo:

#datos1 = pd.DataFrame(np.random.randn(5,3))
#datos2 = pd.DataFrame(np.random.randn(5,3))
#piezas = [datos1, datos2]
datos_concatenados_b = pd.concat(piezas, keys = ['datos1', 'datos2'])
print('datos1n {}'.format(datos1))
print('datos2n {}'.format(datos2))
print('datos_concatenadosn {}'.format(datos_concatenados_b))
datos1
          0         1         2
0 -1.691985 -1.181241 -0.714437
1  0.955094 -0.238498  1.137918
2 -0.533739 -0.285976 -0.990184
3 -0.626446  0.664830  0.278803
4 -0.183818 -0.013190  0.505786

[5 rows x 3 columns]
datos2
          0         1         2
0 -2.063044  2.328388  0.043275
1 -1.720170 -0.039871  0.954244
2 -0.173751  0.047003 -0.979577
3 -0.293044  1.928332 -1.323554
4  0.705127  3.711652 -0.535096

[5 rows x 3 columns]
datos_concatenados
                 0         1         2
datos1 0 -1.691985 -1.181241 -0.714437
       1  0.955094 -0.238498  1.137918
       2 -0.533739 -0.285976 -0.990184
       3 -0.626446  0.664830  0.278803
       4 -0.183818 -0.013190  0.505786
datos2 0 -2.063044  2.328388  0.043275
       1 -1.720170 -0.039871  0.954244
       2 -0.173751  0.047003 -0.979577
       3 -0.293044  1.928332 -1.323554
       4  0.705127  3.711652 -0.535096

[10 rows x 3 columns]

Vemos que hay índices repetidos pero están en 'grupos' diferentes. De esta forma, si queremos acceder a la fila con índice 0 del primer grupo de datos concatenados (datos1) podemos hacer lo siguiente:

print(datos_concatenados_b.ix['datos1'].ix[0])
<pre>0   -1.691985
1   -1.181241
2   -0.714437
Name: 0, dtype: float64</pre>
Estamos viendo filas, pero podemos hacer los mismo para las columnas, por supuesto, usando el nombre de la columna (en el ejemplo siguiente, la columna 0):
[code language="Python"]
print(datos_concatenados_b.ix['datos1'][0])
0   -1.691985
1    0.955094
2   -0.533739
3   -0.626446
4   -0.183818
Name: 0, dtype: float64

Vemos qué tipo de índice es este índice 'compuesto' que hemos creado:

datos_concatenados_b.index
MultiIndex(levels=[['datos1', 'datos2'], [0, 1, 2, 3, 4]],
labels=[[0, 0, 0, 0, 0, 1, 1, 1, 1, 1], [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]])

Vemos que es un MultiIndex. No vamos a ver mucho más pero os lo dejo anotado para que sepáis que existen combinaciones de índices (o de columnas) y se manejan de forma un poco más compleja que un índice 'simple'. Se conoce como indexación jerárquica y permiten ser un poco más descriptivos (verbose) con nuestros DataFrames aunque conlleva un punto más de complejidad a la hora de trabajar con los datos.

¿Qué pasa cuando una de las columnas no es igual en los grupos de datos que queramos concatenar? El nuevo DataFrame tendrá en cuenta este aspecto rellenando con NaNs donde convenga. Veamos el siguiente código de ejemplo:

datos1 = pd.DataFrame(np.random.randn(5,3))
datos2 = pd.DataFrame(np.random.randn(5,4))
piezas = [datos1, datos2]
datos_concatenados_c = pd.concat(piezas, ignore_index = True)
print('datos1n {}'.format(datos1))
print('datos2n {}'.format(datos2))
print('datos_concatenadosn {}'.format(datos_concatenados_c))
datos1
          0         1         2
0 -0.082729 -0.016452 -1.280156
1  0.606336 -0.504770 -2.017690
2 -2.147009 -0.632275  0.023689
3 -0.255461 -0.042007  0.661835
4  2.351576  0.735611 -0.187072

[5 rows x 3 columns]
datos2
          0         1         2         3
0 -0.223023  0.070622 -0.577119 -1.430177
1 -1.661289 -0.214221  0.709818 -0.642611
2 -0.098368 -0.489105 -1.373906 -2.104431
3  0.880578 -0.601151 -1.450542 -0.289738
4 -1.461346 -0.539262  0.327825 -0.944431

[5 rows x 4 columns]
datos_concatenados
          0         1         2         3
0 -0.082729 -0.016452 -1.280156       NaN
1  0.606336 -0.504770 -2.017690       NaN
2 -2.147009 -0.632275  0.023689       NaN
3 -0.255461 -0.042007  0.661835       NaN
4  2.351576  0.735611 -0.187072       NaN
5 -0.223023  0.070622 -0.577119 -1.430177
6 -1.661289 -0.214221  0.709818 -0.642611
7 -0.098368 -0.489105 -1.373906 -2.104431
8  0.880578 -0.601151 -1.450542 -0.289738
9 -1.461346 -0.539262  0.327825 -0.944431

[10 rows x 4 columns]

Vemos que el primer grupo de datos, datos1, solo tiene tres columnas mientras que el segundo grupo, datos2, tiene 4 columnas. El resultado final tendrá en cuenta esto y rellenerá la columna 3 que pertenece a los datos del primer grupo de datos, datos1. Cool!

Lo visto hasta ahora para concatenar Series o DataFrames lo podemos hacer también usando el método append. Veamos un ejemplo similar a lo anterior:

datos1 = pd.DataFrame(np.random.randn(5,3))
datos2 = pd.DataFrame(np.random.randn(5,4))
datos_concatenados_d = datos1.append(datos2, ignore_index = True)
print('datos1n {}'.format(datos1))
print('datos2n {}'.format(datos2))
print('datos_concatenadosn {}'.format(datos_concatenados_d))
datos1
          0         1         2
0 -0.974367  1.732370  0.354479
1 -0.021746  2.215287  1.107243
2  0.018506  1.301015  1.103651
3 -1.857281 -1.181981  0.097104
4 -0.595689  0.140885  1.993213

[5 rows x 3 columns]
datos2
          0         1         2         3
0 -0.211180 -0.093403  0.215210 -0.154284
1  0.206997  1.277379 -0.893895 -0.216731
2 -1.138390 -0.067240  1.688928 -2.191215
3  0.938069  0.174496 -1.722735 -0.873746
4  0.177425  0.823896 -0.595673 -0.426416

[5 rows x 4 columns]
datos_concatenados
          0         1         2         3
0 -0.974367  1.732370  0.354479       NaN
1 -0.021746  2.215287  1.107243       NaN
2  0.018506  1.301015  1.103651       NaN
3 -1.857281 -1.181981  0.097104       NaN
4 -0.595689  0.140885  1.993213       NaN
5 -0.211180 -0.093403  0.215210 -0.154284
6  0.206997  1.277379 -0.893895 -0.216731
7 -1.138390 -0.067240  1.688928 -2.191215
8  0.938069  0.174496 -1.722735 -0.873746
9  0.177425  0.823896 -0.595673 -0.426416

[10 rows x 4 columns]

Otra vez hemos llegado al final. ¡¡Estad atentos a la próxima entrega!!

Kiko Correoso

Licenciado y PhD en Ciencias Físicas, especializado en temas de física, meteorología, climatología, energías renovables, estadística, aprendizaje automático, análisis y visualización de datos. Apasionado de Python y su comunidad. Fundador de pybonacci y editor del sitio en el que se divulga Python, Ciencia y el conocimiento libre en español.

More Posts

Follow Me:
TwitterLinkedIn

Leave a Reply

Your email address will not be published. Required fields are marked *