Tabla de Contenidos

5. Pandas

Pandas es una librería en cierto sentido similar a NumPy. Pero si NumPy únicamente contiene vectores, matrices, tensores ,etc junto con operaciones matemáticas. Con pandas tenemos mas cosas como nombrar a las columnas con un nombre , incluir un índices o generación de gráficas.

Mas información:

Importación

  import pandas as pd

DataFrames

tipo=['SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD']
capacidad=[0.5, 0.5, 0.24, 0.48, 1, 0.512, 1, 0.48, 0.12, 0.96, 0.256, 0.512, 1, 2, 0.5, 2, 2, 1, 1.5, 4, 2, 4, 6, 5, 8, 10, 12, 14, 16, 18]
precio=[101, 51, 27, 44, 86, 101, 138, 50, 22, 83, 41, 78, 126, 183, 91, 48, 51, 37, 55, 81, 48, 88, 187, 146, 240, 360, 387, 443, 516, 612]



data=zip(tipo,capacidad,precio)
columns=['tipo', 'capacidad','precio']

df=pd.DataFrame(data, columns=columns)

La función zip es similar a np.column_stack de numpy pero ha usado zip en vez de column_stack ya que hay datos de tipos string. Si todos los datos hubieran sido números de podría haber usado column_stack

df=df.append({'tipo':"SSD","capacidad":1,"precio":214},ignore_index=True)

Mas información:

Acceso a disco

df.to_csv("datos.csv", index=False)

Es importante añadir index=False ya que sino creará en disco una columna extra con el nº de la fila a modo de índice

tipo,capacidad,precio
SSD,0.5,101
SSD,0.5,51
SSD,0.24,27
SSD,0.48,44
........
HDD,14.0,443
HDD,16.0,516
HDD,18.0,612
SSD,1.0,214

df=pd.read_csv("datos.csv",sep=",")

Si al guardar los datos NO se añadió el parámetro index=False ,al leer el fichero se deberá añadir el parámetro index_col=0
df.to_csv("datos.csv")
df=pd.read_csv("datos.csv",index_col=0)

import sqlalchemy

connection = sqlalchemy.create_engine('mysql+pymysql://mi_usuario:mi_contrasenya@localhost:3306/mi_database')
df=pd.read_sql("SELECT * FROM mi_tabla",con=connection)

Crear un DataFrame desde una base de datos relacional es tan sencillo como crear la conexión con sqlalchemy.create_engine y luego con pandas llamar a read_sql.

Previamente hay que instalar sqlalchemy con:

conda install -c anaconda sqlalchemy

Información

df.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 31 entries, 0 to 30
Data columns (total 3 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   tipo       31 non-null     object 
 1   capacidad  31 non-null     float64
 2   precio     31 non-null     int64  
dtypes: float64(1), int64(1), object(1)
memory usage: 872.0+ bytes

df.shape

(31, 3)

df.head()

  tipo  capacidad  precio
0  SSD       0.50     101
1  SSD       0.50      51
2  SSD       0.24      27
3  SSD       0.48      44
4  SSD       1.00      86

df.tail()

   tipo  capacidad  precio
26  HDD       12.0     387
27  HDD       14.0     443
28  HDD       16.0     516
29  HDD       18.0     612
30  SSD        1.0     214

Estadística

df.describe()

       capacidad      precio
count  28.000000   28.000000
mean    4.092143  157.428571
std     5.209273  158.877178
min     0.120000   22.000000
25%     0.512000   50.750000
50%     1.750000   89.500000
75%     5.250000  184.000000
max    18.000000  612.000000

df.precio.mean()

157.42

df.precio.std()

158.87

	 
df.precio.sum()	 

4408.0

	 
df.precio.max()	 

612.0

	 
df.precio.min()	 

22.0

df.corr()

           capacidad    precio
capacidad   1.000000  0.962542
precio      0.962542  1.000000

Acceder a los datos

Lo normal es que queramos acceder a los datos siempre por columnas. Así que explicaremos únicamente esa forma.

df['capacidad']
df.loc[:,'capacidad']
df.capacidad

Las tres formas son equivalentes pero fíjate que al usar la función loc hay que indicar que queremos todas las filas para ello usamos el :

df[['precio','capacidad']]
df.loc[:,['precio','capacidad']]

df.loc[:,df.columns!='tipo']

df
df[:]
df.loc[:,:]

df.iloc[:,1]

df.iloc[:,-1]

df.iloc[:,[0,-1]]

Columnas

df.columns

['tipo', 'capacidad', 'precio']

len(df.columns)

df.columns[0]

#Cambiamos el nombre de la columna "tipo" al nuevo nombre "target"
df.rename(columns={'tipo': 'target'}, inplace=True)
new_df=df.rename(columns={'tipo': 'target'})

  • Notar que al indicar inplace=True se hace la modificación en el propio DataFrame y no hace falta asignarlo a otro nuevo DataFrame
  • Lo normal es que la columna a predecir la llamemos target

#Ahora la columna target está al final
df=df.reindex(columns=['capacidad','precio','target'])

  • Notar que es necesario asignar el DataFrame a un nuevo DataFrame
  • Lo normal es que la columna a predecir se ponga al final

datos_nueva_columna=[1000, 1250, 6500, 2500, 2750, 2500, 1000, 1500, 2250, 5500, 2750, 4250, 5000, 3750, 2500, 6500, 5250, 5250, 3250, 3500, 7250, 6250, 2250, 3500, 4250, 6000, 2000, 3000, 5250, 2500]

df.insert(0,"velocidad",datos_nueva_columna)

datos_nueva_columna=[1000, 1250, 6500, 2500, 2750, 2500, 1000, 1500, 2250, 5500, 2750, 4250, 5000, 3750, 2500, 6500, 5250, 5250, 3250, 3500, 7250, 6250, 2250, 3500, 4250, 6000, 2000, 3000, 5250, 2500]


df.insert(2,"velocidad",datos_nueva_columna)

df.insert(3,"calculada",df.precio*df.capacidad)

  tipo  capacidad  precio  calculada
0  SSD       0.50     101      50.50
1  SSD       0.50      51      25.50
2  SSD       0.24      27       6.48
3  SSD       0.48      44      21.12
4  SSD       1.00      86      86.00
...........

#Pasamos de euros a dolares
df.precio=df.precio*1.13

df.drop(columns = ['calculada'], inplace = True)
new_df=df.drop(columns = ['calculada'])

Filtrado

df[df.tipo=='SSD']

df[(df.tipo=='SSD') & (df.precio<100)]

Lo que retornan estos métodos es un nuevo DataFrame así que se pueden aplicar todos los métodos de los DataFrame.

df.tipo.unique()

array(['SSD', 'HDD'], dtype=object)

Datos inválidos

Vamos ahora a crear un DataFrame con datos inválidos.

tipo=[None, 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 'SSD', 
     'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD', 'HDD']
capacidad=[0.5, math.nan, 0.24, None, 1, 0.512, 1, 0.48, 0.12, 0.96, 0.256, 0.512, 1, 2, 0.5, 2, 2, 1, 1.5, 4, 2, 4, 6, 5, 8, 10, 12, 14, 16, 18]
precio=[101, 51, math.nan, 44, None, 101, 138, 50, 22, 83, 41, 78, 126, 183, 91, 48, 51, 37, 55, 81, 48, 88, 187, 146, 240, 360, 387, 443, 516, 612]
 
 
 
data=zip(tipo,capacidad,precio)
columns=['tipo', 'capacidad','precio']
 
df=pd.DataFrame(data, columns=columns)

df.isnull().sum()

tipo         1
capacidad    2
precio       2
dtype: int64

df=df.dropna()
df.isnull().sum()

tipo         0
capacidad    0
precio       0
dtype: int64

Gráficas con Seaborn

Con dataframes lo sencillo usar seaborn

figure=plt.figure(figsize=(12,8))
axes = figure.add_subplot()

sns.scatterplot(x="capacidad", y="precio", hue="tipo",data=df,ax=axes)

figure=plt.figure(figsize=(16,5))
axes = figure.add_subplot(1,2,1)
sns.kdeplot(x="capacidad",data=df,fill=True,ax=axes)
axes = figure.add_subplot(1,2,2)
sns.kdeplot(x="precio",data=df,fill=True,ax=axes)

figure=plt.figure(figsize=(16,5))
axes = figure.add_subplot(1,2,1)
sns.kdeplot(x="capacidad",hue="tipo",data=df,fill=True,ax=axes)
axes = figure.add_subplot(1,2,2)
sns.kdeplot(x="precio",hue="tipo",data=df,fill=True,ax=axes)

figure=plt.figure(figsize=(16,5))
axes = figure.add_subplot(1,2,1)
sns.histplot(x="capacidad",hue="tipo",data=df,ax=axes)
axes = figure.add_subplot(1,2,2)
sns.histplot(x="precio",hue="tipo",data=df,ax=axes)

pairplot=sns.pairplot(df,hue="tipo")

Ejercicios

Ejercicio 1.A

Crea un DataDrame con los datos que proporciona load_iris. Recuerda que la propiedad feature_names retorna los nombres. La columna de los tipos de flor la debes llamar tipo_flor

Ejercicio 1.B

Siguiendo con el DataFrame anterior imprime por pantalla:

Ejercicio 1.C

Siguiendo con el DataFrame anterior:

Ejercicio 1.D

Siguiendo con el DataFrame anterior imprime por pantalla:

Ejercicio 1.E

Siguiendo con el DataFrame anterior y usando Seaborn:

Ejercicio 2.A

Descarga el siguiente fichero tiempos_red_neuronal.csv que contiene los segundos que ha tardado en entrenarse una red neuronal según el nº de épocas y la función de activación usada

Ejercicio 2.B

Siguiendo con el DataFrame anterior:

¿Ves algo raro en los datos?

Ejercicio 2.C

Siguiendo con el DataFrame anterior:

Ejercicio 2.D

Siguiendo con el DataFrame anterior:

La gráfica no deja claro cda una de las rectas. Para mejorarlo se podría anotar al final de cada recta el nombre de la función de activación tal y como se muestra en el siguiente gráfico:

Para poner texto en una gráfica se usa el método annotate y la librería adjustText - automatic label placement for matplotlib

Ejercicio 3.A

Usando la función plot_metrics muestra las gráficas de las pérdidas por épocas de las siguientes redes neuronales para el DataFrame de las flores:

Nº Neuronas en cada capa
2,4,2,1
4,8,4,1
8,16,8,1
4,8,4,2,1
8,16,8,4,1
16,32,16,8,1
32,64,32,8,1
64,128,64,8,1
8,16,32,64,32,16,8,1

Además:

Ejercicio 3.B

Siguiendo con el DataFrame anterior:

$$ valor = \frac {X-min}{max-min} $$

$$ valor = \frac {X-media}{desviación} $$

Ejercicio 3.C

Usando la función plot_metrics muestra las gráficas de las pérdidas por épocas de las siguientes redes neuronales para el DataFrame de las flores:

Nº Neuronas en cada capa
2,4,2,1
4,8,4,1
8,16,8,1
4,8,4,2,1
8,16,8,4,1
16,32,16,8,1
32,64,32,8,1
64,128,64,8,1
8,16,32,64,32,16,8,1

Pero para cada tipo de red ,deberás entrenarla 3 veces distintas y obtener resultado distintos con:

Además:

¿Hay diferencias al usar los datos normalizados o estandarizado? ¿Cual es mejor y peor? ¿Las ventajas son las mismas independientemente de la red?