Processing math: 100%

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

1
import pandas as pd

DataFrames

1
2
3
4
5
6
7
8
9
10
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

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

Mas información:

Acceso a disco

1
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

1
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
1
2
df.to_csv("datos.csv")
df=pd.read_csv("datos.csv",index_col=0)

1
2
3
4
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:

1
conda install -c anaconda sqlalchemy

Información

1
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

1
df.shape

(31, 3)

1
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

1
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

1
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

1
df.precio.mean()

157.42

1
df.precio.std()

158.87

1
2
      
df.precio.sum() 

4408.0

1
2
      
df.precio.max() 

612.0

1
2
      
df.precio.min() 

22.0

1
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.

1
2
3
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 :

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

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

1
2
3
df
df[:]
df.loc[:,:]

1
df.iloc[:,1]

1
df.iloc[:,-1]

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

Columnas

1
df.columns

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

1
len(df.columns)

1
df.columns[0]

1
2
3
#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

1
2
#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

1
2
3
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)

1
2
3
4
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)

1
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
...........

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

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

Filtrado

1
df[df.tipo=='SSD']

1
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.

1
df.tipo.unique()

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

Datos inválidos

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

1
2
3
4
5
6
7
8
9
10
11
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)

1
df.isnull().sum()

tipo         1
capacidad    2
precio       2
dtype: int64

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

tipo         0
capacidad    0
precio       0
dtype: int64

Gráficas con Seaborn

Con dataframes lo sencillo usar seaborn

1
2
3
4
figure=plt.figure(figsize=(12,8))
axes = figure.add_subplot()
 
sns.scatterplot(x="capacidad", y="precio", hue="tipo",data=df,ax=axes)

1
2
3
4
5
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)

1
2
3
4
5
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)

1
2
3
4
5
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)

1
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=Xminmaxmin

valor=Xmediadesviació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?