Entrada

Resumen del curso 'Pandas' de Kaggle (1) - Lecciones 1–3

Resumen práctico del uso de la librería Pandas para limpiar y transformar datos. Sintetizamos y, cuando conviene, ampliamos el curso abierto de Kaggle. Esta entrada cubre las lecciones 1–3.

Resumen del curso 'Pandas' de Kaggle (1) - Lecciones 1–3

Aquí recopilo lo estudiado a través del curso de Pandas de Kaggle.
Como la extensión es considerable, lo he separado en 2 partes.

Certificate of Completion

Lección 1. Creación, lectura y escritura

Importar pandas

1
import pandas as pd

En pandas hay dos objetos fundamentales: DataFrame y Series.

DataFrame

Un DataFrame puede verse como una tabla o una matriz. Está compuesto por una matriz de entradas independientes, donde cada entrada tiene un valor y corresponde a una fila o registro y a una columna.

1
pd.DataFrame({'Yes': [50, 21], 'No': [131, 2]})
 YesNo
050131
1212

Las entradas de un DataFrame no tienen por qué ser numéricas; por ejemplo, este DataFrame contiene cadenas (reseñas de usuarios):

1
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 'Sue': ['Pretty good.', 'Bland.']})
 BobSue
0I liked it.Pretty good.
1It was awful.Bland.

Para crear un DataFrame se usa el constructor pd.DataFrame() y se declara con la sintaxis de diccionario de Python: la clave es el nombre de la columna y el valor es una lista con los elementos de esa columna. Es el método estándar para declarar un DataFrame nuevo.

Al declarar un DataFrame, puedes especificar etiquetas de columnas; si no especificas etiquetas de filas, se asignan enteros 0, 1, 2, … Puedes establecer manualmente las etiquetas de fila cuando lo necesites. La lista de etiquetas de fila se llama índice y se puede fijar con el parámetro index del constructor.

1
2
3
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 
              'Sue': ['Pretty good.', 'Bland.']},
             index=['Product A', 'Product B'])
 BobSue
Product AI liked it.Pretty good.
Product BIt was awful.Bland.

Series

Una Series es una secuencia de valores, o un vector.

1
pd.Series([1, 2, 3, 4, 5])

Una Series es, en esencia, equivalente a una única columna de un DataFrame. Puedes asignarle igualmente un índice, y en lugar de “nombre de columna” tiene simplemente un “nombre” (name).

1
pd.Series([30, 35, 40], index=['12015 Sales', '12016 Sales', '12017 Sales'], name='Product A')
1
2
3
4
12015 Sales    30
12016 Sales    35
12017 Sales    40
Name: Product A, dtype: int64

Series y DataFrame están estrechamente relacionados; puedes pensar en un DataFrame como un conjunto de Series.

Leer archivos de datos

En muchos casos no crearemos los datos a mano, sino que cargaremos datos existentes. Los datos pueden almacenarse en muchos formatos; el más básico es CSV. Un CSV suele verse así:

Product A,Product B,Product C,
30,21,9,
35,34,1,
41,11,11

Es decir, un CSV es una tabla con valores separados por comas (Comma-Separated Values, CSV).

Para cargar un CSV en un DataFrame se usa pd.read_csv().

1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv")

Con la propiedad shape puedes ver la forma del DataFrame.

1
product_reviews.shape
1
(129971, 14)

La salida anterior indica que el DataFrame tiene 129971 registros y 14 columnas.

Con el método head() ves las primeras cinco filas.

1
product_reviews.head()

La función pd.read_csv() tiene más de 30 parámetros. Por ejemplo, si el CSV ya contiene un índice, puedes usar el parámetro index_col para indicar qué columna debe usarse como índice en lugar de que pandas cree uno automáticamente.

1
product_reviews = pd.read_csv("../input/product-reviews/example-data.csv", index_col=0)

Escribir archivos de datos

Con el método to_csv() puedes exportar un DataFrame a un archivo CSV. Por ejemplo:

1
product_reviews.to_csv("../output/product-reviews/example-data.csv")

Lección 2. Indexación, selección y asignación

Seleccionar valores concretos en un DataFrame o una Series es un paso casi universal en el procesamiento de datos; conviene aprender a seleccionar rápidamente los puntos de datos que necesitas.

Accesos nativos de Python

Los objetos nativos de Python ofrecen buenas formas de indexación, y pandas adopta esos mismos métodos.

Atributos del objeto

En Python, accedemos al valor de un atributo de un objeto con su nombre. Si example_obj tiene el atributo title, lo llamamos como example_obj.title. Podemos hacer lo mismo con las columnas de un DataFrame de pandas:

1
reviews.country
1
2
3
4
5
6
0            Italy
1         Portugal
            ...   
129969      France
129970      France
Name: country, Length: 129971, dtype: object

Indexación de diccionarios

El tipo diccionario de Python permite acceder a sus valores con el operador de indexación ([]). Igual con las columnas de un DataFrame:

1
reviews['country']
1
2
3
4
5
6
0            Italy
1         Portugal
            ...   
129969      France
129970      France
Name: country, Length: 129971, dtype: object

Ambos métodos son válidos, pero la indexación estilo diccionario permite manejar nombres de columnas con caracteres especiales como el espacio en blanco (p. ej., reviews['country providence'] es posible, mientras que reviews.country providence no lo es).

Dentro de la Series resultante, puedes volver a usar [] para leer valores individuales:

1
reviews['country'][0]
1
'Italy'

Accesos propios de pandas

Además de los métodos anteriores, pandas ofrece dos accesores específicos: loc y iloc.

Selección basada en índice

Con iloc haces selección basada en índice: eliges por posición (enteros).

Por ejemplo, para seleccionar la primera fila del DataFrame:

1
reviews.iloc[0]
1
2
3
4
5
6
country                                                    Italy
description    Aromas include tropical fruit, broom, brimston...
                                     ...                        
variety                                              White Blend
winery                                                   Nicosia
Name: 0, Length: 13, dtype: object

A diferencia del estilo Python nativo (columna primero y luego fila), iloc selecciona primero filas y luego columnas. Para la primera columna:

1
reviews.iloc[:, 0]
1
2
3
4
5
6
0            Italy
1         Portugal
            ...   
129969      France
129970      France
Name: country, Length: 129971, dtype: object

En el ejemplo anterior, : selecciona todas las filas y luego tomamos la primera columna. Para seleccionar la segunda (1) y tercera (2) fila de la primera columna:

1
reviews.iloc[1:3, 0]
1
2
3
1    Portugal
2          US
Name: country, dtype: object

O pasando una lista:

1
reviews.iloc[[1, 2], 0]
1
2
3
1    Portugal
2          US
Name: country, dtype: object

También puedes usar índices negativos para contar desde el final. Por ejemplo, las últimas 5 filas:

1
reviews.iloc[-5:]

Selección basada en etiqueta

Con loc haces selección basada en etiqueta: eliges por el valor del índice.

Por ejemplo, para obtener la entrada de la fila con índice 0 y la columna ‘country’:

1
reviews.loc[0, 'country']
1
'Italy'

iloc ignora los valores del índice y trata el dataset como una gran matriz; loc utiliza la información del índice. Dado que el índice suele contener información significativa, loc suele ser más intuitivo.

Diferencias en los rangos de iloc y loc

iloc sigue la convención estándar de Python: 0:10 significa 0 inclusivo y 10 exclusivo, es decir, 0,...,9.

loc interpreta los rangos como cerrados: 0:10 significa de 0 a 10 inclusive, 0,...,10.

La razón es que loc admite índices de cualquier tipo estándar, no solo enteros. Por ejemplo, si el índice es alfabético y quieres seleccionar de ‘Apples’ a ‘Potatoes’, resulta más natural escribir df.loc['Apples':'Potatoes'] que calcular manualmente el límite superior exclusivo.

Por lo demás, su comportamiento es similar.

En datasets con índices enteros ordenados ascendentemente, cuando necesito rangos con :, prefiero iloc para evitar confusiones por la diferencia de inclusividad; en otros casos, loc me resulta más intuitivo.

Manipular el índice

Puedes ajustar el índice según convenga. Con set_index() puedes establecer una columna como nuevo índice:

1
reviews.set_index("title")

Selección condicional

Hasta ahora hemos usado propiedades estructurales del DataFrame para seleccionar datos. También podemos filtrar por condiciones más complejas.

Por ejemplo, en un DataFrame con información de vinos, supongamos que queremos los vinos italianos con puntuación ≥ 90.

1
reviews.country == 'Italy'

La condición devuelve una Series booleana de True/False:

1
2
3
4
5
6
0          True
1         False
          ...  
129969    False
129970    False
Name: country, Length: 129971, dtype: bool

loc es basado en etiquetas, pero también acepta arrays booleanos o Series booleanas alineables. Por tanto, podemos seleccionar solo vinos de Italia:

1
reviews.loc[reviews.country == 'Italy']

Puedes combinar varias condiciones con & u |. Para vinos de Italia y con puntuación ≥ 90:

1
reviews.loc[(reviews.country == 'Italy') & (reviews.points >= 90)]

Para vinos de Italia o con puntuación ≥ 90:

1
reviews.loc[(reviews.country == 'Italy') | (reviews.points >= 90)]

Pandas incluye selectores condicionales muy útiles como isin e isnull/notnull.

isin devuelve una máscara booleana que indica si el valor está “en” una lista dada. Por ejemplo, para seleccionar vinos de Italia o Francia:

1
reviews.loc[reviews.country.isin(['Italy', 'France'])]

isna/notna sirven para filtrar valores faltantes (NaN). Por ejemplo, para quedarnos solo con vinos cuyo precio no está ausente:

1
reviews.loc[reviews.price.notna()]

Nota: aunque no aparece en el curso de Kaggle, iloc también acepta arrays booleanos. A diferencia de loc, solo admite arrays y no Series, por lo que su uso en composiciones como las anteriores es más limitado.

Asignación de datos

Puedes crear o sobrescribir columnas en un DataFrame.

1
2
reviews['critic'] = 'everyone'
reviews['critic']
1
2
3
4
5
6
0         everyone
1         everyone
            ...   
129969    everyone
129970    everyone
Name: critic, Length: 129971, dtype: object
1
2
reviews['index_backwards'] = range(len(reviews), 0, -1)
reviews['index_backwards']
1
2
3
4
5
6
0         129971
1         129970
           ...  
129969         2
129970         1
Name: index_backwards, Length: 129971, dtype: int64

Lección 3. Funciones de resumen y maps

Visión general de los datos

El método describe() ofrece un resumen de alto nivel de una columna dada.

1
reviews.points.describe()
1
2
3
4
5
6
count    129971.000000
mean         88.447138
             ...      
75%          91.000000
max         100.000000
Name: points, Length: 8, dtype: float64

La salida de describe() depende del tipo. Para datos de texto, devuelve:

1
reviews.taster_name.describe()
1
2
3
4
5
count         103727
unique            19
top       Roger Voss
freq           25514
Name: taster_name, dtype: object

También puedes obtener estadísticas puntuales:

1
reviews.points.mean()
1
88.44713820775404
1
reviews.taster_name.unique()
1
2
3
4
5
6
7
array(['Kerin O’Keefe', 'Roger Voss', 'Paul Gregutt',
       'Alexander Peartree', 'Michael Schachner', 'Anna Lee C. Iijima',
       'Virginie Boone', 'Matt Kettmann', nan, 'Sean P. Sullivan',
       'Jim Gordon', 'Joe Czerwinski', 'Anne Krebiehl\xa0MW',
       'Lauren Buzzeo', 'Mike DeSimone', 'Jeff Jenssen',
       'Susan Kostrzewa', 'Carrie Dykes', 'Fiona Adams',
       'Christina Pickard'], dtype=object)

Para contar apariciones de cada valor único en un DataFrame, usa value_counts().

1
reviews.taster_name.value_counts()
1
2
3
4
5
6
Roger Voss           25514
Michael Schachner    15134
                     ...  
Fiona Adams             27
Christina Pickard        6
Name: taster_name, Length: 19, dtype: int64

Mapeos (Maps)

Un mapeo (map) es un término tomado de las matemáticas que significa una función que asocia un conjunto con otro. En ciencia de datos, a menudo transformamos datos a otra representación; para ello usamos mapeos, que son fundamentales.

Los dos métodos más usados son:

Series.map() recibe una función que transforma un valor en otro valor y la aplica a todos los valores de la Series, devolviendo una Series nueva. Por ejemplo, para restar la media a todas las puntuaciones:

1
2
review_points_mean = reviews.points.mean()
reviews.points.map(lambda p: p - review_points_mean)
1
2
3
4
5
6
0        -1.447138
1        -1.447138
            ...   
129969    1.552862
129970    1.552862
Name: points, Length: 129971, dtype: float64

DataFrame.apply() aplica una función personalizada a cada fila, transformando el DataFrame completo.

1
2
3
4
5
def remean_points(row):
    row.points = row.points - review_points_mean
    return row

reviews.apply(remean_points, axis='columns')

Con axis='index', apply() aplica la función a cada columna en lugar de a cada fila.

Tanto Series.map() como DataFrame.apply() devuelven un objeto nuevo (transformado) y no modifican los datos originales.

MétodoSeries.map()DataFrame.apply()
ÁmbitoSeriesDataFrame
Unidad de aplicaciónValor a valor
(si consideras la Series como vector columna, se aplica por filas)
Por filas por defecto
Con opción para aplicar por columnas

Nota: también existen Series.apply() y DataFrame.map().

  • Series.apply():
    • by_row='compat' (valor por defecto): funciona como Series.map()
    • by_row=False: pasa la Series completa a la función (similar a DataFrame.apply() con axis='index')
  • DataFrame.map(): aplica función a cada valor del DataFrame (análogo a Series.map(), pero a nivel de DataFrame)

De hecho, pandas admite de forma nativa muchas operaciones típicas de mapeo. El ejemplo anterior puede escribirse de forma mucho más simple, y pandas entenderá la intención:

1
2
review_points_mean = reviews.points.mean()
reviews.points - review_points_mean
1
2
3
4
5
6
0        -1.447138
1        -1.447138
            ...   
129969    1.552862
129970    1.552862
Name: points, Length: 129971, dtype: float64

Además, pandas permite operaciones entre Series de la misma longitud. En el ejemplo de vinos, podemos concatenar país y región como cadenas:

1
reviews.country + " - " + reviews.region_1
1
2
3
4
5
6
0            Italy - Etna
1                     NaN
               ...       
129969    France - Alsace
129970    France - Alsace
Length: 129971, dtype: object

Estas operaciones usan optimizaciones internas, por lo que suelen ser más rápidas que map() o apply(). Aun así, map() y apply() son más flexibles y permiten tareas más complejas; conviene conocerlos.

Esta entrada está licenciada bajo CC BY-NC 4.0 por el autor.