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…
¿Qué es Pandas?
Pandas es una librería que proporciona estructuras de datos flexibles y permite trabajar con la información de forma eficiente (gran parte de Pandas está implementado usando C/Cython para obtener un buen rendimiento).
Funciona muy bien cuando nos toca trabajar con:
- Datos heterogéneos que pueden distribuirse de forma tabular.
- Series temporales
- Matrices
- …
La página oficial se encuentra en el siguiente enlace.
Estructuras de datos
Pandas ofrece varias estructuras de datos que nos resultarán de mucha utilidad y que vamos a ir viendo poco a poco. Todas las posibles estructuras de datos que ofrece a día de hoy son:
- Series (y TimeSeries)
- DataFrame
- Panel
- Panel4D
- PanelND
Series
En una instancia de la clase Series
podremos almacenar arrays o vectores con índice o etiqueta. Si no usamos índice o etiqueta nos lo numerará con un índice de forma interna. La forma básica de crear una Series sería:
s = Series(data, index=index)
donde data
es el vector de datos e index
(opcional) es el vector de índices que usará la serie.
Si los índices son datos de fechas directamente se creará una instancia de una TimeSeries en lugar de una instacia de Series.
Veamos un ejemplo de como crear este tipo de contenedor de datos. Primero vamos a crear una serie y pandas nos creará índices automáticamente, segundo vamos a crear una serie donde nosotros le vamos a decir los índices que queremos usar y, tercero, vamos a crear una serie temporal usando índices que son fechas.
# serie con índices automáticos serie = pd.Series(np.random.randn(10)) print(u'Serie con índices automáticos\n{}\n'.format(serie)) print(type(serie))
Cuyo resultado sería:
Serie con índices automáticos 0 -1.731792 1 0.492089 2 0.190005 3 0.498570 4 -0.129411 5 0.496611 6 -0.561550 7 -0.981822 8 -0.471803 9 -0.255938 dtype: float64 <class 'pandas.core.series.Series'>
# serie con índices definidos por mi serie = pd.Series(np.random.randn(4), index = ['itzi','kikolas','dieguete','nicolasete']) print(u'Serie con índices definidos n{}\n'.format(serie)) print(type(serie))
Cuyo resultado sería el siguiente:
Serie con índices definidos itzi 0.130432 kikolas -2.378303 dieguete 0.951302 nicolasete 1.846942 dtype: float64 <class 'pandas.core.series.Series'>
# serie(serie temporal) con índices que son fechas serie = pd.Series(np.random.randn(31), index = pd.date_range('2013/01/01', periods = 31)) print(u'Serie temporal con índices de fechas\n{}\n'.format(serie)) print(type(serie))
Cuyo resultado sería:
Serie temporal con índices de fechas 2013-01-01 0.086782 2013-01-02 -0.274399 2013-01-03 -0.919958 2013-01-04 0.749879 2013-01-05 -1.739752 2013-01-06 0.861299 2013-01-07 -0.797413 2013-01-08 -0.650584 2013-01-09 0.880755 2013-01-10 -0.235406 2013-01-11 0.134650 2013-01-12 -0.255786 2013-01-13 -0.068295 2013-01-14 0.010727 2013-01-15 0.259768 2013-01-16 2.449214 2013-01-17 -1.452189 2013-01-18 -0.846709 2013-01-19 -0.835064 2013-01-20 -0.073190 2013-01-21 0.853177 2013-01-22 -0.239683 2013-01-23 0.149482 2013-01-24 -0.233044 2013-01-25 0.073302 2013-01-26 -1.769805 2013-01-27 -1.016134 2013-01-28 0.378350 2013-01-29 0.190374 2013-01-30 -1.566578 2013-01-31 1.601782 Freq: D, dtype: float64 <class 'pandas.core.series.Series'>
En los ejemplos anteriores hemos creado las series a partir de un numpy array pero las podemos crear a partir de muchas otras cosas: listas, diccionarios, numpy arrays,… Veamos ejemplos:
serie_lista = pd.Series([ii for i in range(10)]) print('Serie a partir de una lista\n{}\n'.format(serie_lista)) dicc = {'cuadrado de {}'.format(i) : ii for i in range(10)} serie_dicc = pd.Series(dicc) print('Serie a partir de un diccionario\n{}\n'.format(serie_dicc)) serie_serie = pd.Series(serie_dicc.values) print('Serie a partir de los valores de otra (pandas)serie\n{}\n'.format(serie_serie)) serie_cte = pd.Series(-999, index = np.arange(10)) print('Serie a partir de un valor constante\n{}\n'.format(serie_cte)) # …
Y el resultado del código anterior sería:
Serie a partir de una lista 0 0 1 1 2 4 3 9 4 16 5 25 6 36 7 49 8 64 9 81 dtype: int64 Serie a partir de un diccionario cuadrado de 0 0 cuadrado de 1 1 cuadrado de 2 4 cuadrado de 3 9 cuadrado de 4 16 cuadrado de 5 25 cuadrado de 6 36 cuadrado de 7 49 cuadrado de 8 64 cuadrado de 9 81 dtype: int64 Serie a partir de los valores de otra (pandas)serie 0 0 1 1 2 4 3 9 4 16 5 25 6 36 7 49 8 64 9 81 dtype: int64 Serie a partir de un valor constante 0 -999 1 -999 2 -999 3 -999 4 -999 5 -999 6 -999 7 -999 8 -999 9 -999 dtype: int64
Una serie (Series o TimeSeries) se puede manejar igual que si tuviéramos un numpy array de una dimensión o igual que si tuviéramos un diccionario.
Vemos ejemplos de esto:
serie = pd.Series( np.random.randn(10), index = ['a','b','c','d','e','f','g','h','i','j'] ) print('Serie que vamos a usar en este ejemplo: \n{}\n'.format(serie))</code> # Ejemplos de comportamiento como numpy array print('Se comporta como un numpy array:') print('================================') print('>>> serie.max()\n{}'.format(serie.max())) print('>>> serie.sum()\n{}'.format(serie.sum())) print('>>> serie.abs()\n{}'.format(serie.abs())) print('>>> serie[serie > 0]\n{}'.format(serie[serie > 0])) # … print('n') # Ejemplos de comportamiento como diccionario print("Se comporta como un diccionario:") print("================================") print(">>> serie['a']\n{}".format(serie['a'])) print(">>> 'a' in serie\n{}".format('a' in serie)) print(">>> 'z' in serie\n{}".format('z' in serie))
Y el resultado será:
Serie que vamos a usar en este ejemplo: a -0.663462 b 0.893211 c -0.312062 d -1.196054 e 1.608529 f -0.045494 g -0.192866 h 1.681370 i -0.503151 j -0.078362 dtype: float64 Se comporta como un numpy array: ================================ >>> serie.max() 1.6813703206498873 >>> serie.sum() 1.1916586064903802 >>> serie.abs() a 0.663462 b 0.893211 c 0.312062 d 1.196054 e 1.608529 f 0.045494 g 0.192866 h 1.681370 i 0.503151 j 0.078362 dtype: float64 >>> serie[serie > 0] b 0.893211 e 1.608529 h 1.681370 dtype: float64 Se comporta como un diccionario: ================================ >>> serie['a'] -0.663462354872233 >>> 'a' in serie True >>> 'z' in serie False
Las operaciones están ‘vectorizadas’ y se hacen elemento a elemento con los elementos alineados en función del índice. Si se hace, por ejemplo, una suma de dos series, si en una de las dos series no existe un elemento, i.e. el índice no existe en la serie, el resultado para ese índice será nan. En resumen, estamos haciendo una unión de los índices y funciona diferente a los numpy arrays. Se puede ver el esquema en el siguiente ejemplo:
s1 = serie[1:] s2 = serie[:-1] suma = s1 + s2 print(' s1 s2 s1 + s2') print('------------------ ------------------ ------------------') for clave in sorted(set(list(s1.keys()) + list(s2.keys()))): print( '{0:1} {1:20} + {0:1} {2:20} = {0:1} {3:20}'.format( clave, s1.get(clave), s2.get(clave), suma.get(clave) ) ) # En la anterior línea de código uso el método get para no obtener un KeyError # como sí obtendría si uso, p.e., s1['a']
Cuyo resultado será:
s1 s2 s1 + s2 ------------------ ------------------ ------------------ a None + a -0.663462354872233 = a nan b 0.8932105297090326 + b 0.8932105297090326 = b 1.7864210594180652 c -0.31206215624310224 + c -0.31206215624310224 = c -0.6241243124862045 d -1.1960537478238258 + d -1.1960537478238258 = d -2.3921074956476516 e 1.6085289802454341 + e 1.6085289802454341 = e 3.2170579604908682 f -0.0454943963901047 + f -0.0454943963901047 = f -0.0909887927802094 g -0.19286571253906507 + g -0.19286571253906507 = g -0.38573142507813013 h 1.6813703206498873 + h 1.6813703206498873 = h 3.3627406412997747 i -0.503150771440017 + i -0.503150771440017 = i -1.006301542880034 j -0.07836208480562608 + j None = j nan
DataFrame
Un DataFrame se puede ver como si fuera una tabla con índices para las filas y las columnas. Es algo similar a lo que tenemos en una hoja de cálculo, una tabla de una BBDD SQL o un diccionario de series Pandas. Esta será la estructura de datos más habitual a usar. El DataFrame se puede crear a partir de muchas otras estructuras de datos:
- A partir de un diccionario de numpy arrays de 1D, diccionarios de listas, diccionarios de diccionarios o diccionarios de Series
- A partir de un numpy array de 2D
- A partir de un numpy array estructurado (structured ndarray o record ndarray)
- A partir de una Series de Pandas
- A partir de otro DataFramede Pandas
Además de los datos, podemos definir los índices (las etiquetas para las filas) o las columnas (etiquetas para las mismas). Si no se define nada al crear el DataFrame se usarán normas del sentido común para nombrar los índices de filas y columnas.
Veamos un poco de código para ver esto:
df_lista = pd.DataFrame({'a': [11,12,13], 'b': [21,22,23]}) print('DataFrame a partir de un diccionario de listas\n{}\n'.format(df_lista)) df_np1D = pd.DataFrame({'a': np.arange(3)**2, 'b': np.random.randn(3)}) print('DataFrame a partir de un diccionario de 1D ndarrays\n{}\n'.format(df_np1D)) df_np2D = pd.DataFrame( np.empty((5,3)), index=['primero','segundo','tercero','cuarto','quinto'], columns=['velocidad', 'temperatura','presion'] ) print('DataFrame a partir de un 2D ndarray\n{}\n'.format(df_np2D)) df_df = pd.DataFrame(df_np2D, index = ['primero','segundo','tercero']) df_df.index = ['first','second','third'] print('DataFrame a partir de los valores de otro (pandas)DataFrame\n{}\n'.format(df_df)) # …
Y el resultado de todo lo anterior sería:
DataFrame a partir de un diccionario de listas a b 0 11 21 1 12 22 2 13 23 [3 rows x 2 columns] DataFrame a partir de un diccionario de 1D ndarrays a b 0 0 -0.818739 1 1 -1.473575 2 4 -0.014659 [3 rows x 2 columns] DataFrame a partir de un 2D ndarray velocidad temperatura presion primero 1.397157e-306 4.311076e-314 0.000000e+00 segundo 1.670358e-179 -1.238962e-65 -2.110672e-44 tercero -3.645480e-42 0.000000e+00 -1.561315e-65 cuarto -2.623582e-42 0.000000e+00 1.519743e-314 quinto -2.578083e-42 0.000000e+00 2.413050e-312 [5 rows x 3 columns] DataFrame a partir de los valores de otro (pandas)DataFrame velocidad temperatura presion first 1.397157e-306 4.311076e-314 0.000000e+00 second 1.670358e-179 -1.238962e-65 -2.110672e-44 third -3.645480e-42 0.000000e+00 -1.561315e-65 [3 rows x 3 columns]
Podemos construir un DataFrame a partir de constructores alternativos como pd.DataFrame.from_dict
, pd.DataFrame.from_records
o pd.DataFrame.from_items
.
Panel, Panel4D, PanelND
En general, los paneles son para tipos de datos de más de dos dimensiones. No los vamos a cubrir ya que se consideran un pelín más complejos, de uso menos habitual y/o se encuentran en estado experimental con lo que pueden cambiar bastante en el corto/medio plazo. Se puede consultar la documentación oficial pulsando sobre:
- Panel
- Panel4D
- PanelND
Y de momento es suficiente por hoy. En breve dejaremos una segunda parte… ¡¡Estad atentos!!
Pingback: Reseña del libro “Learning NumPy Array” de Ivan Idris | Pybonacci
Kiko, gracias por el curso.
Estoy comenzando con Python. IPython Notebook me corre con Python 2. ¿Cómo puedo hacer que corra con Python 3?
instala Python3 y luego ipython para Python3.
Puedes usar un virtualenv o un entorno de conda.
Ahora estoy con el móvil pero luego te dejo un par de enlaces
Tengo una duda:
Cuando construyes el DataFrame a partir de un numpy array 2D, no arrojarría un dataframe de 5×3 vacío? Cuándo le has pasado el random…?
Muchas Gracias.
como diría mi hija:
“Maestro”
No.
El DataFrame se te creará con lo que le digas que lo cree. Si usas numpy.random.randn(5,3), por ejemplo, te creará un dataframe con valores aleatorios con *shape* (5, 3).
Si quieres crear un dataframe vacio puedes hacer ‘df = pd.DataFrame()’ pero este dataframe no tendrá estructura (filas x columnas) hasta que le metas datos.