Pandas en Python, con ejemplos -Parte II- Lectura-Escritura, Merge & GroupBy


El proyecto de este post lo puedes descargar pulsando AQUI.

Sobre la librería de Pandas vamos a hablar de los siguientes temas en las entradas:

  1. Parte I: Introducción a Pandas.
  2. Parte II: DataFrame: Lectura y Escritura, Mergeo de DataFrame's y GroupBy.
  3. Parte III: Operaciones de "Pivot_table" con DataFrame's.

Lectura-Escritura, Merge y GroupBy

En este tutorial vamos a mostrar algunas de las operaciones y funcionalidades que nos aporta la librería de Pandas para trabajar con DataFrame's. Para ello utilizaremos el data set del sistema de recomendación de MovieLens que tiene información sobre películas, usuarios y sobre los votos que emiten los usuarios a las películas. A través del siguiente enlace (http://grouplens.org/datasets/movielens/) podéis descargaros este data set (con diferente número de votos: 100K, 1M, 10M, etc), aunque en el proyecto que hemos compartidos en github están los ficheros del data set que vamos a utilizar (con 100K votos).

Aunque en el data set de Movilens vienen varios ficheros con diferente información; para el ejemplo que vamos a realizar, solo utilizaremos los datos de los usuarios, películas y votos. Como estos datos los ha compartido Movilens obteniendolos de su base de datos, la relación entre estos ficheros quedaría de la siguiente manera (en un diagrama Entidad-Relación):

MovieLens_ER

Como vemos, tenemos informacion relativa a los usuarios y películas, y como relación N-M tenemos la tabla intermedia de votos.

Lectura-Escritura

Con la libreria Pandas podemos leer los data set a partir de diferentes formatos; como por ejemplo txt, csv, json, sql, html, y un largo etc. En el siguiente enlace http://pandas.pydata.org/pandas-docs/stable/io.html podéis ver todos los posibles formatos. Cada uno de estos métodos de lectura de determinados formatos (read_NombreFormato) tiene infinidad de parámetros que podéis ver en la documentación y que no vamos a explicar por lo extensísima que seria esta explicación. Sin embargo si que paso a explicar el método "generico" para la lectura de ficheros sea cual sea su formato; como es el caso del método "read_table()". Este método también puede tener infinidad de parámetros, pero siendo prácticos y para la mayoría de los casos que nos vamos a encontrar los parámetros serían los siguientes:


pd.read_table(dir_fichero, engine='python', sep=';', header=True|False, names=[lista con nombre columnas])


Básicamente hay que pasarle el fichero a leer, cual es su separador, si la primera linea del fichero contiene el nombre de las columnas y en el caso de que no las tenga pasarle en 'names' el nombre de las columnas. Veamos por ejemplo como leeriamos el fichero con los datos de los usuarios, siendo el contenido de las 5 primeras lineas el siguiente:

1::F::1::10::48067
2::M::56::16::70072
3::M::25::15::55117
4::M::45::7::02460
5::M::25::20::55455

Vemos que la primera linea no tiene el nombre de las cabeceras; por tanto, hay que indicarle el 'header=False'. También vemos que el separador de las columnas es '::' y el nombre de las columnas es el que indicamos en el diagrama Entidad-Relación anterior. Por tanto para leer los datos de los usuarios a partir del fiecho txt y pasarlos a un DataFrame habría que hacer lo siguiente:

# Load users info
userHeader = ['user_id', 'gender', 'age', 'ocupation', 'zip']
users = pd.read_table('dataSet/users.txt', engine='python', sep='::', header=None, names=userHeader)

# print 5 first users
print '# 5 first users: \n%s' % users[:5]

Como salida a este fragmento de código en el que al final imprimimos por pantalla los 5 primeros usuarios quedaría de la siguiente forma:

# 5 first users: 
   user_id gender  age  ocupation    zip
0        1      F    1         10  48067
1        2      M   56         16  70072
2        3      M   25         15  55117
3        4      M   45          7  02460
4        5      M   25         20  55455

Para escribir un DataFrame en un fichero de texto se pueden utilizar los método de escritura (ver en http://pandas.pydata.org/pandas-docs/stable/io.html) para escribirlos en el formato que se quiera. Por ejemplo si utilizamos el método 'to_csv()' nos escribirá el DataFrame en este formato estandar que separa los campos por comas; pero por ejemplo, podemos decirle al método que en vez de que utilice como separador una coma, que utilice por ejemplo un guión. Si queremos escribir en un fichero el DataFrame 'users' con estas características lo podemos hacer de la siguiente manera:


users.to_csv('MyUsers.txt', sep='-')


Para este ejemplo nos genera un fichero de texto llamado 'MyUsers.txt' con el siguiente contenido, en el que vemos que en la primera linea nos escribe el nombre de las columnas y en la siguiente el contenido del DataFrame, pero incluyendo en la primera posición el índice:

-user_id-gender-age-ocupation-zip
0-1-F-1-10-48067
1-2-M-56-16-70072
2-3-M-25-15-55117
3-4-M-45-7-02460
4-5-M-25-20-55455
5-6-F-50-9-55117
.................
.................

Merge

Una funcionalidad muy potente que ofrece Pandas es la de poder mergear (en bases de datos sería hacer un JOIN) datos siempre y cuando este sea posible. En el ejemplo que estamos haciendo con el data set de MovieLens podemos ver esta funcionalidad de forma muy intuitiva, ya que los datos de este data set se han obtenido a partir de una bases de datos relacional. Veamos a continuación como hacer un JOIN o un mergeo de los ficheros 'users.txt' y 'ratings.txt' a partir del 'user_id':

# Load users info
userHeader = ['user_id', 'gender', 'age', 'ocupation', 'zip']
users = pd.read_table('dataSet/users.txt', engine='python', sep='::', header=None, names=userHeader)

# Load ratings
ratingHeader = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_table('dataSet/ratings.txt', engine='python', sep='::', header=None, names=ratingHeader)

# Merge tables users + ratings by user_id field
merger_ratings_users = pd.merge(users, ratings)
print '# Merge tables users + ratings by user_id field \n%s' % merger_ratings_users[:10]

Como resultado a este fragmento de código tenemos las 10 primeras posiciones de esta unión:

# Merge tables users + ratings by user_id field 
   user_id gender  age  ocupation    zip  movie_id  rating  timestamp
0        1      F    1         10  48067      1193       5  978300760
1        1      F    1         10  48067       661       3  978302109
2        1      F    1         10  48067       914       3  978301968
3        1      F    1         10  48067      3408       4  978300275
4        1      F    1         10  48067      2355       5  978824291
5        1      F    1         10  48067      1197       3  978302268
6        1      F    1         10  48067      1287       5  978302039
7        1      F    1         10  48067      2804       5  978300719
8        1      F    1         10  48067       594       4  978302268
9        1      F    1         10  48067       919       4  978301368

De la misma forma que hemos hecho el JOIN de los usuarios y los votos, podemos hacer lo mismo añadiendo también los datos relativos a las películas:

# Load Data
userHeader = ['user_id', 'gender', 'age', 'ocupation', 'zip']
users = pd.read_table('dataSet/users.txt', engine='python', sep='::', header=None, names=userHeader)

movieHeader = ['movie_id', 'title', 'genders']
movies = pd.read_table('dataSet/movies.txt', engine='python', sep='::', header=None, names=movieHeader)

ratingHeader = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_table('dataSet/ratings.txt', engine='python', sep='::', header=None, names=ratingHeader)

# Merge data
mergeRatings = pd.merge(pd.merge(users, ratings), movies)

Si quisiésemos ver por ejmplo un elemento de este nuevo JOIN creado (por ejemplo la posición 1000), lo podríamos hacer de la siguiente forma:

info1000 = mergeRatings.ix[1000]
print 'Info of 1000 position of the table: \n%s' % info1000[:10]

Teniendo como resultado lo siguiente:

Info of 1000 position of the table: 
user_id                                        3612
gender                                            M
age                                              25
ocupation                                        14
zip                                           29609
movie_id                                       1193
rating                                            5
timestamp                                 966605873
title        One Flew Over the Cuckoo's Nest (1975)
genders                                       Drama
Name: 1000, dtype: object
 

GroupBy

La última funcionalidad que vamos a ver en este tutorial es la de agrupación de datos en los DataFrame's que correspondería a la operación 'groupby' que se puede hacer en las bases de datos. Con esta funcionalidad no solo permite hacer agregaciones; sino también, hacer operaciones en base a esas agregaciones como el calculo de sumas, medias, desviaciones típicas, etc. Para los ejemplos que vamos a ver a continución necesitaremos utilizar la librería 'numpy' para hacer este tipo de operaciones, por tanto hay que importarla en nuestro proyecto:


import pandas as pd
import numpy as np


Para ver un ejemplo de la operación 'groupby', vamos a unir primero las tres fuentes de datos (users, movies y ratings) y vamos a agrupar por el título de la película (columna 'title') y vamos a contar cuantos votos ha recibido cada película (haciendo un conteo del número de tuplas). Posteriormente haremos una ordenación de mayor a menos para ver las 10 más votadas. Esto lo hacemos de la siguiente manera:

# Load Data
userHeader = ['user_id', 'gender', 'age', 'ocupation', 'zip']
users = pd.read_table('dataSet/users.txt', engine='python', sep='::', header=None, names=userHeader)

movieHeader = ['movie_id', 'title', 'genders']
movies = pd.read_table('dataSet/movies.txt', engine='python', sep='::', header=None, names=movieHeader)

ratingHeader = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_table('dataSet/ratings.txt', engine='python', sep='::', header=None, names=ratingHeader)

# Merge data
mergeRatings = pd.merge(pd.merge(users, ratings), movies)

# Clone DataFrame
def cloneDF(df):
    return pd.DataFrame(df.values.copy(), df.index.copy(), df.columns.copy()).convert_objects(convert_numeric=True)

# Show Films with more votes. (groupby + sorted)
numberRatings = cloneDF(mergeRatings)
numberRatings = numberRatings.groupby('title').size().sort_values(ascending=False)
print 'Films with more votes: \n%s' % numberRatings[:10]

Vemos como con el método 'groupby()' nos permite agrupar por la columna que le indiquemos, con el método 'size()' nos hace la cuenta del número de veces que se repiten los títulos tras la agrupación y por último com el método 'sort_values()' nos ordena el resultado de mayor a menor, teniendo como resultado lo siguiente:

Films with more votes: 
title
American Beauty (1999)                                   3428
Star Wars: Episode IV - A New Hope (1977)                2991
Star Wars: Episode V - The Empire Strikes Back (1980)    2990
Star Wars: Episode VI - Return of the Jedi (1983)        2883
Jurassic Park (1993)                                     2672
Saving Private Ryan (1998)                               2653
Terminator 2: Judgment Day (1991)                        2649
Matrix, The (1999)                                       2590
Back to the Future (1985)                                2583
Silence of the Lambs, The (1991)                         2578
dtype: int64

ADVERTENCIA!! para la copia o "clone" de DataFrame's:

Por lo general para la mayoría de las estructuras de datos en python suele haber un método 'copy()' o 'clone()' que te hace una copia de una estructura de datos determinada. Para el caso de los DataFrame's (aunque si buscáis por internet veréis algunos ejemplos que no funcionan) no se aplican estos métodos y para crearse una copia de un DataFrame, hay que construirse otro DataFrame con los mismos datos del DataFrame original. A continuación comparto un método ('cloneDF(df)') devuelve una copia del DataFrame que se pasa como parámetro:


# Clone DataFrame
def cloneDF(df):
    return pd.DataFrame(df.values.copy(), df.index.copy(), df.columns.copy()).convert_objects(convert_numeric=True)


Probablemente exista una solución mejor a la que aquí se propone, pero esta solución funciona.

Para ver que más cosas se pueden hacer, podemos hacer la agrupación por más columnas; como por ejemplo, por título e identificador de la película y en este caso calcularemos la nota media de los votos por película con el método 'mean()':

# Show avg ratings movie (groupby + avg)
avgRatings = cloneDF(mergeRatings)
avgRatings = avgRatings.groupby(['movie_id', 'title']).mean()
print 'Avg ratings: \n%s' % avgRatings['rating'][:10]
 
Avg ratings: 
movie_id  title                             
1         Toy Story (1995)                      4.146846
2         Jumanji (1995)                        3.201141
3         Grumpier Old Men (1995)               3.016736
4         Waiting to Exhale (1995)              2.729412
5         Father of the Bride Part II (1995)    3.006757
6         Heat (1995)                           3.878723
7         Sabrina (1995)                        3.410480
8         Tom and Huck (1995)                   3.014706
9         Sudden Death (1995)                   2.656863
10        GoldenEye (1995)                      3.540541
Name: rating, dtype: float64

En este caso hemos indicado que nos muestre el valor medio del campo 'rating', pero al aplicar el método 'mean()' en el DataFrame nos ha calculado todas las médias de todas las columnas que tienen datos numéricos.

Ahora seguimos agrupando por titulo e identificador, pero además vamos a hacer varios cálculos simultáneos que le indicaremos con una lista que le pasamos al método 'agg()':

# Show data ratings movies (groupby + several funtions)
dataRatings = cloneDF(mergeRatings)
dataRatings = dataRatings.groupby(['movie_id', 'title'])['rating'].agg(['mean', 'sum', 'count', 'std'])
print 'Films ratings info: \n%s' % dataRatings[:10]
Films ratings info: 
                                                 mean   sum  count       std
movie_id title                                                              
1        Toy Story (1995)                    4.146846  8613   2077  0.852349
2        Jumanji (1995)                      3.201141  2244    701  0.983172
3        Grumpier Old Men (1995)             3.016736  1442    478  1.071712
4        Waiting to Exhale (1995)            2.729412   464    170  1.013381
5        Father of the Bride Part II (1995)  3.006757   890    296  1.025086
6        Heat (1995)                         3.878723  3646    940  0.934588
7        Sabrina (1995)                      3.410480  1562    458  0.979918
8        Tom and Huck (1995)                 3.014706   205     68  0.954059
9        Sudden Death (1995)                 2.656863   271    102  1.048290
10       GoldenEye (1995)                    3.540541  3144    888  0.891233

Al método 'agg()' le podemos pasar también funciones declaradas por nosotros mismos como es el siguiente caso en el que hacemos una función que nos calcula la media de los votos y para ver que es correcto, indicamos también que nos calcule la media (en este caso vamos a utilizar las funciones de la librería numpy):

# Show data ratings movies, applying a function (groupby + lambda function)
myAvg = cloneDF(mergeRatings)
myAvg = myAvg.groupby(['movie_id', 'title'])['rating'].agg(
    {'SUM': np.sum, 'COUNT': np.size, 'AVG': np.mean, 'myAVG': lambda x: x.sum() / float(x.count())})
print 'My info ratings: \n%s' % myAvg[:10]
My info ratings: 
                                             COUNT   SUM       AVG     myAVG
movie_id title                                                              
1        Toy Story (1995)                     2077  8613  4.146846  4.146846
2        Jumanji (1995)                        701  2244  3.201141  3.201141
3        Grumpier Old Men (1995)               478  1442  3.016736  3.016736
4        Waiting to Exhale (1995)              170   464  2.729412  2.729412
5        Father of the Bride Part II (1995)    296   890  3.006757  3.006757
6        Heat (1995)                           940  3646  3.878723  3.878723
7        Sabrina (1995)                        458  1562  3.410480  3.410480
8        Tom and Huck (1995)                    68   205  3.014706  3.014706
9        Sudden Death (1995)                   102   271  2.656863  2.656863
10       GoldenEye (1995)                      888  3144  3.540541  3.540541

Como último ejemplo vamos a agrupar por tíyulo e identificador y vamos a calcular el número de votos recibidos por película, calculamos la nota media y por último ordenamos las películas por el número de votos para poder ver la nota media de las películas más votadas:

# Sort data ratings by created field (groupby + lambda function + sorted)
sortRatingsField = cloneDF(mergeRatings)
sortRatingsField = sortRatingsField.groupby(['movie_id', 'title'])['rating'].agg(
    {'COUNT': np.size, 'myAVG': lambda x: x.sum() / float(x.count())}).sort('COUNT', ascending=False)
print 'My info sorted: \n%s' % sortRatingsField[:15] 
My info sorted: 
                                                             COUNT     myAVG
movie_id title                                                              
2858     American Beauty (1999)                               3428  4.317386
260      Star Wars: Episode IV - A New Hope (1977)            2991  4.453694
1196     Star Wars: Episode V - The Empire Strikes Back ...   2990  4.292977
1210     Star Wars: Episode VI - Return of the Jedi (1983)    2883  4.022893
480      Jurassic Park (1993)                                 2672  3.763847
2028     Saving Private Ryan (1998)                           2653  4.337354
589      Terminator 2: Judgment Day (1991)                    2649  4.058513
2571     Matrix, The (1999)                                   2590  4.315830
1270     Back to the Future (1985)                            2583  3.990321
593      Silence of the Lambs, The (1991)                     2578  4.351823
1580     Men in Black (1997)                                  2538  3.739953
1198     Raiders of the Lost Ark (1981)                       2514  4.477725
608      Fargo (1996)                                         2513  4.254676
2762     Sixth Sense, The (1999)                              2459  4.406263
110      Braveheart (1995)                                    2443  4.234957

CONCLUSIONES:

La librería de Pandas es una librería destinada al análisis de datos muy utilizada por los 'data scientists'. Con esta librería se pueden hacer infinidad de cosas y en este caso hemos visto algunos casos prácticos utilizando el data set de MovieLens que es junto con el data set de Netflix son los data sets de referencia para el mundo de la investigación de los Sistemas de Recomendación (filtrado colaborativo principalmente) y Machine Learning. Dado que es un tutorial de introducción se han visto pocas cosas sobre esta librería, pero si que se han visto las necesarias como para empezar a trabajar con Pandas y una vez que se tenga soltura con esta librería seguro que podréis hacer un análisis de datos muy exhaustivo y preciso ya que es una librería que utilizan hoy en dia los data scientists profesionales.

Comparte esta entrada en:
Safe Creative #1401310112503
Pandas en Python, con ejemplos -Parte II- Lectura-Escritura, Merge & GroupBy por "www.jarroba.com" esta bajo una licencia Creative Commons
Reconocimiento-NoComercial-CompartirIgual 3.0 Unported License.
Creado a partir de la obra en www.jarroba.com

Un comentario en “Pandas en Python, con ejemplos -Parte II- Lectura-Escritura, Merge & GroupBy”

  1. Si tengo una archivo de datos con extensión .csv y deseo ordenar solo una porción del archivo de menor a mayor por el campo temperatura, por ejemplo, como podría hacer esto?. Supongamos que tenemos esto:
    id,type,xs,ys,zs
    500,1,0.413068,0.247238,0.139937
    384,1,0.509008,0.225649,0.113632
    391,1,0.596777,0.212693,0.0827694
    91,1,0.554459,0.155958,0.15268
    396,1,0.678003,0.227263,0.134453
    183,1,0.263006,0.32626,0.129268
    199,1,0.355738,0.315005,0.0961778
    572,1,0.398526,0.368158,0.0291984
    563,1,0.449962,0.296985,0.0700937
    710,1,0.431955,0.334981,0.152336
    612,1,0.486912,0.343823,0.000879648
    754,1,0.541953,0.275987,0.0351425
    737,1,0.529449,0.313131,0.123254
    y que quisiera ordenar una porción de este archivo, por ejemplo, por el campo id, usando el módulo pandas. Como podría hacer esto?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies

ACEPTAR
Aviso de cookies