Pandas (parte 1)
(Φ ₒ Φ) Esta es la introducción a Pandas que me hubiera gustado tener
- Estructuras de datos
- Carga de datos
- Inspección
- Indexación
- Índices jerárquicos
- Operaciones vectorizadas
- Selección usando columnas
- Alineamiento automático
- Ejemplo integrador
- Visualización básica
- Actividad de investigación
- Recursos adicionales
import pandas as pd
lista = ['hidrógeno', 'helio', 'litio']
lista
Podemos referenciar un valor coleccionado utilizando el operador índice [ ]
. Las listas indexan por posición. En Python se empieza contar desde cero, por lo tanto las posiciones también.
lista[0]
lista[0] = '?'
lista
Los diccionarios son colecciones de pares etiqueta:valor
e indexan por etiqueta. No es posible referenciar a un elemento por posición.
diccionario = {
'H' :'hidrógeno',
'He':'helio',
'Li':'litio',
}
diccionario
diccionario['H']
diccionario['Be'] = 'berilio'
diccionario
Pandas
Una Series
es un objeto unidimensional, similar a una columna en una tabla. Se comporta como una lista y también le asigna una etiqueta a cada elemento en la colección, comportándose como un diccionario. Por defecto, cada elemento recibirá una etiqueta que va de 0 a N-1, donde N es la longitud/tamaño de la colección.
pd.Series(['hidrógeno', 'helio', 'litio'])
Nota: dtype es el tipo de los elementos de la colección. En este caso object
hace referencia a objetos en general, es decir, cualquier tipo de objeto: entero (int
), flotante (float
), texto (str
), etcétera.
pd.Series(['hidrógeno', 'helio', 'litio'], index=['H', 'He', 'Li'])
s = pd.Series({
'H' :'hidrógeno',
'He':'helio',
'Li':'litio',
})
s
s.index
El índice es un objeto del tipo Index
, aportado por Pandas.
s.values
Los valores se coleccionan en arreglos de NumPy (objetos del tipo array
). Los arreglos son similares a las listas, tienen más funcionalidades.
s['H']
s['Be'] = 'berilio'
s
Un DataFrame
es una estructura tabular (bidimensional) compuesta por filas y columnas, similar a una hoja de cálculo, una tabla de una base de datos, o a un data.frame
del lenguaje R.
Se puede pensar al DataFrame
como una Series
de Series
: una Series
cuyo índice son los nombres de las columnas y cuyos elementos son Series
que se comportan como columnas. El índice de las Series
-columna son los nombres de las filas.
pd.DataFrame([
[1, 1.008],
[2, 4.003],
[3, 6.941],
[4, 9.012]
])
df = pd.DataFrame(
[[1, 1.008],
[2, 4.003],
[3, 6.941],
[4, 9.012]],
index=['H', 'He', 'Li', 'Be'],
columns=['número_atómico', 'masa_atómica']
)
df
Otras formas de inicializar un DataFrame
, usando:
- diccionario de listas
- diccionario de diccionarios
- lista de diccionarios
df.index
df.columns
df.values
Nota: Los nombres de columnas también son un índice (Index
). Recapitulando, las Series
tiene un índice y los DataFrame
s tienen dos. Como Pandas piensa la tabla como una colección de columnas, para obtener un valor de una celda primero hay que acceder a la columna y luego a la fila.
df['masa_atómica']
df['nombre'] = s
df
df['masa_atómica']['He']
Nota: Los índices se corresponden con las dimensiones del objeto, un valor de una celda no tiene índices, por lo que es un objeto cero-dimensional, también llamado escalar.
Carga de datos
Ver más: http://pandas.pydata.org/pandas-docs/stable/io.html
Pandas importa y exporta datos de y hacia gran cantidad de formatos.
df = pd.read_csv('datos/properati_básico.csv.gz')
Para exportar un DataFrame
a un archivo CSV podemos usar
df.to_csv('archivo.csv')
df.head()
El método info
nos informa de ambos índices (filas y columnas), incluyendo cantidades. Nombres de columnas, tipos de datos, cantidades de valores no nulos, uso de memoria RAM.
df.info()
El método describe
muestra estadísticas básicas acerca de las columnas númericas. Aplica sobre todas las columnas númericas, incluso sobre aquellas en las que no tiene sentido hacer estadísticas (lat y lon por ejemplo).
df.describe()
Indexación
Por indexación nos referimos a la selección de un subconjunto de un DataFrame
o de una Series
. Por ahora hemos visto un uso básico de los corchetes [ ]
, el operador índice, para acceder a columnas de un DataFrame
y a valores de una Series
. En el caso de las Series
los casos de uso del operador no difieren prácticamente de lo que se puede hacer con un arreglo de NumPy, sin embargo en el caso de los DataFrame
s se complejiza ya que permite seleccionar por filas (no lo vimos) o por columnas (sí lo vimos).
Los corchetes existen en Pandas por conveniencia para hacer algunas operaciones más simples, lo cual también traerá aparejado limitaciones. Pandas va a determinar si estamos queriendo acceder a la tabla usando el índice que se llama index (nombres filas) o el que se llama columns.
A su vez hay dos maneras de utilizar los índices:
- por etiqueta (como en los diccionarios),
- por posición (como en las listas).
Esta modalidad dual no está presente en los diccionarios, que no pueden ser indexados por posición, ni en las listas, que no pueden ser indexadas por etiqueta. Está presente en Pandas y nos permite acceder a los datos de la forma conveniente según el caso.
df['barrio'].head()
No es posible obtener una sola fila con [ ]
, pero sí un subconjunto de filas y también un subconjunto de columnas. Antes de mostrar cómo hacerlo, introducimos tres nuevos conceptos: slice
, lista de índices y máscara booleana.
Cortes
Existe en Python un tipo de objeto llamado slice
, traducido como corte o rebanada, cuyo fin es ayudar a producir sublistas a partir de listas, "cortando" una lista desde una posición inicial hasta una posición final. En realidad la lista original no es modificada sino que se devuelve una nueva, usando los elementos que ya existen en la original (los objetos no son copiados).
frutas = ['naranja', 'banana', 'ananá', 'durazno', 'uva']
frutas
Sabemos cómo obtener valores individuales.
frutas[2]
Supongamos que nos interesa conseguir una sublista con las tres primeras frutas, serían los elementos en las posiciones 0, 1, 2, por lo que deberíamos cortar desde 0 hasta 3 (no inclusive).
corte = slice(0, 3)
corte
frutas[corte]
Podemos obtener el mismo resultado con un atajo. Los atajos reciben el nombre de azúcar sintáctica, son expresiones cómodas para idiomas frecuentes, que existen en paralelo a las formas regulares del lenguaje.
frutas[0:3]
frutas[:3]
frutas[3:]
frutas[:]
corte = slice(None)
corte
frutas[corte]
Los arreglos de NumPy son como las listas de Python pero con funcionalidades agregadas. Admiten indexado por posición, por corte y añaden nuevas maneras de formar sub-arreglos, mediante
- listas o rangos de posiciones,
- listas de valores booleanos, llamadas máscaras.
import numpy as np
frutas = np.array(['naranja', 'banana', 'ananá', 'durazno', 'uva'])
frutas
frutas[[0,2,4]]
En Python existe un tipo de objeto llamado rango (range
), que se puede crear —como casi todos los tipos de objetos en el lenguaje— utilizando una función homónima, que representa una colección de números desde un valor inicial hasta un valor final (no inclusive) utilizando un intervalo determinado. A diferencia de slice
, los rangos pueden ser convertidos en listas.
Si se trata de un patrón regular, es mejor definir un rango antes que una lista.
rango = range(0, 5, 2)
rango
list(rango)
frutas[rango]
máscara = [True, False, True, False, True]
frutas[máscara]
Se filtran aquellos elementos cuya posición se corresponde con las posiciones de los valores False
de la máscara, se seleccionan aquellos donde True
. La máscara sirve para ocultar algunas partes y mostrar otras.
Podemos utilizar una lista de etiquetas para obtener un subconjunto de columnas.
df[['barrio', 'ambientes', 'precio']].head()
Podemos utilizar un corte para obtener un subconjunto de filas por posición.
df[:5]
Nota: En muchos casos los nombres de filas van a coincidir con las posiciones de filas, ya que si no especificamos un índice, el índice por defecto que asigna Pandas a las filas va de 0 a N-1, siendo N la cantidad de filas. Estas etiquetas coinciden con la posición.
También podemos utilizar una máscara. Hay formas muy prácticas de generar máscaras, la que se presenta a continuación solo tiene fines demostrativos y jamás volverá a ser utilizada.
cantidad_filas = len(df)
# usando la función np.null armamos un arreglo tan largo como la tabla, lleno de un valor especificado (False)
máscara = np.full(cantidad_filas, False)
# retocamos la máscara para recuperar solo las primeras dos filas
máscara[0] = True
máscara[1] = True
df[máscara]
Resumimos las capacidades de [ ]
:
- Una etiqueta de columna, devuelve una columna.
- Una lista de etiquetas de columnas, devuelve una subtabla.
- Un corte de posiciones de filas, devuelve una subtabla.
- Una máscara de posiciones de filas, devuelve una subtabla.
Se nota el comportamiento de filas por sus posiciones y columnas por sus etiquetas.
Alternativamente se puede acceder a una columna como si fuera un atributo del DataFrame
.
df.barrio.head()
Filas y columnas
¿Qué pasa si queremos filtrar utilizando ambos índices simultáneamente? En principio podríamos hacer
df[columnas][filas]
sin embargo esta manera efectúa dos selecciones, una seguida de la otra, no es simultánea, lo que traerá más adelante implicancias para la asignación. Para esta situación Pandas provee las siguientes formas de indexar.
-
loc[filas, columnas]
para acceder por etiquetas, -
iloc[filas, columnas]
para acceder por posición.
Es un poco confuso al principio, la i de iloc
hace pensar en la fila (o en la columna) número i, o sea en la posición i.
La limitación de estas formas es que no es posible mezclar etiquetas con posiciones; por ejemplo posición de filas y etiquetas de columnas, una combinación bastante deseable.
# el orden que las filas tienen en la tabla (no hace falta entender esta línea en este momento);
# el método sample sirve para obtener una muestra al azar de filas
df = df.sample(frac=1, random_state=42)
df.head()
df.loc[100, 'fecha']
La selección de todas las columnas en R se logra con df[filas,]
pero en Python esta sintaxis no es válida, opciones son df.loc[filas]
o df.loc[filas, :]
.
df.loc[100]
df.loc[[1,10,100], 'fecha']
df.loc[[1,10,100], ['superficie_cubierta', 'superficie_total']]
La selección de todas las filas en R se logra con df[,columnas]
pero en Python esta sintáxis no es válida, se requiere de un corte total slice(None)
o más común, su azúcar sintáctica df.loc[:, columnas]
.
df.loc[:, ['superficie_cubierta', 'superficie_total']].head()
Si bien las máscaras trabajan por posición y la indexación de loc
es por etiquetas, funcionan de todas formas — es confuso pero será uno de los idiomas más útiles e incluso hasta cobrará sentido más adelante.
df.loc[máscara, ['superficie_cubierta', 'superficie_total']]
IMPORTANTE: Pandas hace su propia interpretación de los cortes en la indexación por etiquetas.
- Ambos extremos son inclusivos.
- Van de un valor del índice a otro según su orden en el índice.
list(df.columns)
Por ejemplo, slice('tipo', 'precio')
tiene el mismo efecto que ['tipo', 'lat', 'lon', 'precio']
.
df.loc[100, 'tipo':'precio']
Veamos el caso de las etiquetas de las filas.
list(df.index)[:10]
Un corte desde la tercera etiqueta (14611) hasta la quinta (16962), slice(14611, 16962)
, es como pasar la lista [14611, 22362, 16962]
. Si el orden del índice fuese otro, la selección generada por el mismo corte, sería otra.
df.loc[14611:16962]
df.iloc[:5]
IMPORTANTE: con iloc
las columnas se seleccionan por su posición, no por sus etiquetas.
list(df.columns)
df.iloc[:, [0,1]].head()
IMPORTANTE: iloc
no acepta máscaras.
Resumen
- Como con listas, es posible indexar por posición.
- Como con diccionarios, es posible indexar por etiqueta.
- Como con arreglos de NumPy, es posible indexar por máscaras booleanas.
- Cualquiera de estos indexadores pueden ser escalares (
int
,str
, ...), listas, o cortes. - Funcionan en filas y columnas.
- Funcionan en índices jerárquicos (próximo tema).
Las distintas formas de indexar también sirven para guardar valores.
Supongamos que nos informan que las propiedades con etiquetas 333 y 666 están mal anotadas como departamentos, que en realidad son casas.
df.loc[[333, 666]]
df.loc[[333, 666], 'tipo'] = 'casa'
df.loc[[333, 666]]
Adicionalmente nos reportan que el precio de la propiedad con etiqueta 666 ha sido actualizado.
df.loc[666, ['fecha', 'precio']] = ['2018-10-28', 510000]
# pasamos una lista de etiquetas para que devuelva un DataFrame,
# esto es válido más allá de que sea una lista de un solo elemento
df.loc[[666]]
Índices jerárquicos
Hasta ahora vimos cómo trabajar con índices creados por defecto (cuando las etiquetas van de 0 a N-1) y con índices impuestos al cargar un archivo (las etiquetas de las columnas), sin embargo el índice puede ser modificado según necesidad. Hay varias formas de definir un nuevo índice, una muy común es elegir a una columna para que se convierta en el nuevo índice.
df.set_index('barrio', inplace=True)
df.head()
Nota: Muchos métodos que transforman DataFrame
s nos devuelven un nuevo DataFrame
, mientras que el original permanece intacto. El argumento inplace=True
se usa para que el método transforme el DataFrame
original; en este caso el método no devuelve nada (None
).
Al convertir a la columna barrio en índice, esta columna dejó de existir. Esto quiere decir que no podemos invocar a la columna como antes, haciendo df['barrio']
o df.barrio
, en todo caso podemos pedir df.index
. Es importante recordar que aunque las etiquetas de las filas parezcan una columna, no lo son, así como las etiquetas de las columnas aunque parecen ser una fila, tampoco lo son.
De aquí en más, podemos hacer indexación con las nuevas etiquetas.
df.loc['PALERMO'].head()
resultado = df.loc[['PALERMO','CHACARITA'], :]
resultado.head(2)
resultado.tail(2)
Cuando necesitemos recuperar al índice como columna, podemos traerla de vuelta.
df.reset_index(inplace=True)
df.head()
Pandas tiene la capacidad de convertir a más de una columna en índice, lo que recibe el nombre de multi-índice, en la que cada ex-columna aporta un nivel de indexación, estableciéndose también una jerarquía de indexación. Los valores existentes en las columnas se convierten en etiquetas.
df.set_index(['barrio','tipo'], inplace=True)
df.head()
En este caso barrio es el nivel 0 de indexación, tiene precedencia por sobre tipo, que viene a ser el nivel 1. Vamos a poder referenciar a los niveles por nombre y por posición.
df.index.names
Seleccionar filas utilizando solo el nivel superior es como si trabajásemos con un índice simple, lo que veníamos haciendo.
df.loc['PALERMO'].head()
Antes de continuar es necesario introducir a las tuplas (tuple
), otro tipo de objeto nativo de Python. Son muy parecidas a las listas, se diferencian en algo que no nos importa mucho ahora: las listas son objetos mutables y las tuplas son inmutables. La forma literal de crearlas es utilizando paréntesis en vez de corchetes.
tupla = (1, 2, 3)
tupla
Para hacer una selección utilizando más niveles del multi-índice se requiere escribir las etiquetas deseadas dentro de tuplas respetando la jerarquía de niveles, así (nivel 0, nivel 1, ...)
.
filas = ('PALERMO', 'departamento')
df.loc[filas].head()
filas = (['SAN TELMO','CHACARITA'], 'departamento')
# al usar multi-índice es IMPORTANTE especificar también las columnas, aunque no queramos filtrarlas
df.loc[filas, :].head()
Para seleccionar las propiedades en todos los barrios que son departamentos tenemos que pedir todas las etiquetas del nivel 0 (todos los barrios) y la etiqueta del nivel 1 (departamento).
En principio podríamos pensar lo siguiente, ya que :
representaría todas las etiquetas de los barrios.
filas = (:, 'departamento')
Sin embargo esta sintaxis no es correcta: el azúcar sintáctica de :
solo sirve cuando está encerrado entre corchetes [:]
y en el caso anterior lo está en paréntesis.
Recordemos que :
es una forma abreviada de tipear slice(None)
y que estas dos formas significan lo mismo.
filas = (slice(None), 'departamento')
df.loc[filas, :].head()
Pandas provee una manera de recuperar el azúcar sintáctica.
filas = pd.IndexSlice[:, 'departamento']
df.loc[filas, :].head()
La gente suele escribir menos recurriendo a esta artimaña.
idx = pd.IndexSlice
idx[:, 'departamento']
Otra opción que tenemos a mano para seleccionar todos los departamentos, más simple aunque solo sirve para selección y no para asignación, es el método cross-section (xs
) que selecciona datos según valor de un índice particular.
df.xs('departamento', level='tipo', drop_level=False).head()
No lo hemos mostrado pero el índice de las columnas también puede ser un multi-índice.
df.reset_index(inplace=True)
Operaciones vectorizadas
Vector y arreglo son dos términos intercambiables. El título se refiere a operaciones sobre arreglos de NumPy que se realizan elemento a elemento (element-wise) que también están Pandas por estar construido sobre NumPy. Además estas operaciones están preparadas para tratar con valores ausentes. Ninguna de estas dos funcionalidades está presente en Python puro.
a = np.array([2, 2, 2])
b = np.array([1, 2, np.nan])
a * b
Nota: Los valores ausentes NA
no existen nativamente en Python, como sí existen en R. NumPy lo mitiga usando NaN
(not a number) para representarlos. NaN
es un valor del tipo float
. Como los arreglos de NumPy pueden tener un solo dtype
, un arreglo de enteros es promovido a un arreglo de flotantes y un arreglo de booleanos es llevado a uno de objetos (object
) en presencia de valores ausentes, ya que por ejemplo, no podríamos tener un arreglo con enteros y NaN
(float
) simultáneamente.
Los operadores (+
, -
, ...) terminan invocando a funciones; existen dentro de la sintáxis del lenguaje por practicidad. Cuando alguno de los operandos es un arreglo de NumPy, se invocan funciones de NumPy correspondientes (np.add
, np.substract
, ...) en vez de funciones de Python puro. También las podemos llamar directamente.
np.multiply(a, b)
Como característica, son funciones universales (ufunc
), ya que hacen su trabajo elemento a elemento independientemente de las dimensiones de los argumentos (escalar, columna, tabla).
Pandas a su vez implementa las funciones de NumPy como métodos (aunque en la mayor parte del tiempo usamos operadores).
df.precio.multiply(35).head()
Las operaciones vectorizadas en cualquiera de sus sabores (operadores, funciones, métodos) son ideales para trabajar sobre las columnas existentes y crear nuevas.
df['precio_m2'] = df.precio / df.superficie_total
df.head()
Operadores y funciones comunes
Existen operadores como +
que requieren dos operandos, llamados operadores binarios y operadores como -
(negación) que trabajan con un solo operando, los operadores unarios.
Comparación
== np.equal
!= np.not_equal
< np.less
<= np.less_equal
> np.greater
>= np.greater_equal
Matemáticas
+ np.add
- np.subtract
- np.negate (unario)
* np.multiply
/ np.divide
** np.pow
np.sqrt
np.exp
np.log
Booleanas
& np.bitwise_and
| np.bitwise_or
~ np.bitwise_not (unario)
Flotantes
np.round
np.isna
np.notna
IMPORTANTE: and
y or
realizan una sola evaluación booleana en el objeto entero, mientras que &
y |
realizan múltiples evaluaciones sobre el contenido (elementos de la colección) de los objetos. Cuando trabajamos con arreglos, esto último es lo que normalmente queremos.
a = np.array([True, True, False])
b = np.array([True, False, False])
# bitwise and
a & b
a | b
frutas = pd.Series(['manzana', 'banana'])
frutas.str.upper()
fechas = pd.Series(['1990-01-01', '2018-10-31'])
fechas
fechas = pd.to_datetime(fechas)
fechas
fechas.dt.year
Nota: NumPy no implementa operaciones con texto y tiempo pero Pandas sí.
Selección usando columnas
En vez de usar el índice de las filas podemos seleccionar filas usando las columnas. Para ello generamos máscaras booleanas usando columnas (que de por sí tienen un largo igual al de la tabla) y operaciones que devuelvan valores booleanos. Las operaciones de comparación son bastante útiles en este contexto ya que nos permiten seleccionar filas según valores en las columnas.
df[df.precio < 80000].head()
IMPORTANTE: Por una cuestión de precedencia de operadores, de querer combinar máscaras usando operaciones bitwise, si las máscaras son creadas usando operadores de comparación, deben ser encerradas entre paréntesis. Esto es porque Python resuelve antes los operadores como &
y |
que los operadores como <
e ==
; usamos los paréntesis para indicarle a Python el orden de evaluación pretendido: primero las comparaciones, luego las operaciones bitwise. Como en matemáticas, lo que está dentro de paréntesis debe resolverse primero (tiene precedencia) sobre lo que está afuera.
df[(df.precio < 80000) & (df.ambientes == 3)].head()
df[df.precio.lt(80000) & df.ambientes.eq(3)].head()
Alineamiento automático
En casos en los que intervengan más de una tabla o columna, como cuando queremos añadir una columna nueva a una tabla, podríamos pensar en cuidar aspectos como que la columna tenga el mismo largo que la tabla y que el orden de los elementos en la nueva columna se condiga con el orden de las filas de la tabla. Esto normalmente es así al trabajar con arreglos de NumPy.
Afortunadamente nada de esto debe preocuparnos al usar Pandas. Los índices cumplen una función de gran utilidad en las operaciones y en las asignaciones, donde les elementos son alianeados según sus índices para que todo salga como lo esperamos.
algunas_frutas = pd.Series(['🥝', '🥥', '🍇', '🍌', '🍎'])
algunas_frutas.index = ['kiwi', 'coco', 'uva', 'banana', 'manzana']
algunas_frutas
otras_frutas = pd.Series(['🍇', '🍐', '🍎'])
otras_frutas.index = ['uva', 'pera', 'manzana']
otras_frutas
algunas_frutas + otras_frutas
El índice de la Series
resultante tiene todas las etiquetas de ambos operandos, con los valores resultantes de la operación. Notar que los elementos no estaban alineados.
Nota: Cuando alguno de los operandos es NaN
el resultado de la operación es NaN
.
dg = algunas_frutas.to_frame(name='algunas')
dg
dg['otras'] = otras_frutas
dg
Se respeta el índice de la tabla, es decir no se le agregan las etiquetas que otras_frutas
tiene de más. Los valores se asignan alineados y los valores con etiquetas no presentes en otras_frutas
quedan ausentes en la nueva columna.
Máscaras
La alineación automática funciona aún en el indexado, siempre y cuando la máscara sea una Series
y su índice coincida con el del objeto sobre el cual se indexa, lo que suele ser el caso cuando trabajamos con las columnas del DataFrame
. Lo que veremos a continuación no funcionaría con una máscara que sea un arreglo de NumPy ya que carece de índice.
máscara = df.ambientes.eq(2)
máscara.head()
df[máscara].head()
# (no hace falta entender esta línea en este momento)
al_revés = máscara[::-1]
# el orden de esta máscara es diferente al de la anterior
al_revés.head()
df[al_revés].head()
Nota: Que el indexado haga uso de las etiquetas de la máscara es la razón por la cuál las máscaras funcionan en el indexado por etiquetas (loc
) y no en el indexado por posición (iloc
).
df.loc[df.superficie_cubierta.isnull(), 'superficie_cubierta'] = df.superficie_total
-
loc
para indexar por filas y columnas; necesitamos indexar por etiquetas para hacer referencia a la columna sobre la cual hacer la asignación por su nombre. - Creamos una máscara usando la columna superficie_cubierta y la operación vectorizada
isnull
, que servirá para seleccionar aquellas filas en las que superficie_cubierta esNaN
. - Asignamos los valores presentes en la columna superficie_total y no nos preocupamos por hacer una selección (como la del punto anterior) sobre estos valores ya que la alineación automática se encargará de alinear las filas de ambos lados de la asignación por su etiqueta.
Visualización básica
Ver más: http://pandas.pydata.org/pandas-docs/stable/visualization.html
%matplotlib inline
Tanto Series
como DataFrame
s tienen un atributo llamado plot
que habilita los siguientes métodos para distintos gráficos típicos.
-
line
yarea
para líneas y líneas sólidas. -
bar
ybarh
para barras verticales y horizontales. -
pie
para tortas. -
scatter
para puntos. -
hist
yhexbin
para histogramas e histogramas 2D. -
density
okde
para densidad de probabilidad. -
box
para boxplot.
IMPORTANTE: Cuando graficamos en base a un DataFrame
, cada columna se interpreta como una variable.
df.precio.plot.hist(bins=1000, xlim=(0,500000))
help(pd.DataFrame.sort_values)
Recursos adicionales
Documentación
Artículos
Machetes
Libros
- Python for data science
- Python for data analysis