#!/usr/bin/env python
# coding: utf-8

# # Parte 2: Transformación de columnas en DataFrame

# In[1]:


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

import warnings
warnings.filterwarnings('ignore')

pd.options.display.float_format = '{:.2f}'.format #Desactivar notación científica en pandas:
np.set_printoptions(suppress=True) #Desactivar notación científica en numpy:
pd.set_option('display.max_columns', None) #comando para mostrar todas las columnas


# Cargamos datos

# In[2]:


cliente = pd.read_csv('./data/dimension_cliente.csv', sep='\t', encoding='UTF-8')


# In[3]:


cliente.head()


# In[4]:


cliente.shape


# In[5]:


cliente.info()


# Existen multitud de herramientas que nos van permitir realizar el análisis exploratorio inicial de manera rápida.  Una de ellas es Sweetwiz.
# https://github.com/fbdesignpro/sweetviz

# In[6]:


import sweetviz


# In[7]:


informe = sweetviz.analyze(cliente)


# In[8]:


informe.show_html()


# ¿Qué podéis decirme de la variable CP? ¿Tiene sentido que el informe nos devuelva la media, mediana, ...?

# Otra herramienta Pandas Profiling... https://github.com/pandas-profiling/pandas-profiling

# In[9]:


get_ipython().system('pip install dtale')


# In[10]:


import dtale


# In[11]:


d = dtale.show(cliente)
d.open_browser()


# In[12]:


cliente.info()


# In[13]:


get_ipython().system('pip install pandas-profiling[notebook]')


# Hay muchas mas librerías para la visualización de datos (Autoviz, D-Tale, VAEX, ...)

# In[14]:


# Selección de columnas determinadas y todas las filas
seleccion = cliente.iloc [:,1:4]
seleccion.head()


# In[15]:


# Selección de columnas determinadas y todas las filas
seleccion = cliente.iloc [:,[1,2,4,3]]
seleccion.head()


# In[16]:


# Selección de columnas determinadas y todas las filas
seleccion = cliente [['NIF','Nombre','Apellido2']]
seleccion.head()


# In[17]:


# Selección de columnas y filas determinadas
seleccion = cliente.iloc [1:10,[1,2,4]]
seleccion


# In[18]:


# Selección de la última posición del dataframe
seleccion = cliente.iloc [-1,-1]
seleccion


# In[19]:


# Selección de columnas según criterio

# La selección de columnas la realizamos mediante un array de True/False
filtro = cliente.columns.str.startswith('C')
seleccion = cliente.loc[:, filtro]


# In[20]:


filtro


# In[21]:


seleccion


# In[22]:


# Selección de datos en función del sexo.
# Devuelve un array de True/False con los registros
# que cumplen la condición
filtro = cliente['Sexo'] == "H"


# In[23]:


filtro


# In[24]:


filtro.value_counts()


# In[25]:


seleccion = cliente.loc [filtro,:]
seleccion.head()


# Se supone que en selección ahora, no debería hacer registros con sexo M.  Lo comprobamos...

# In[26]:


filtro = seleccion ['Sexo'] == "M"
seleccion.loc [filtro, :].value_counts()


# También podemos filtrar por "algo"

# In[27]:


filtro = cliente ['idCliente'] < 50
# filtro = cliente.idCliente < 50
seleccion = cliente.loc [filtro,:]
seleccion.head()


# Miramos los idCliente únicos

# In[28]:


cliente['idCliente'].unique()


# ¿Se os ocurre una manera rápida de ver si en el array previo faltan clientes?

# In[29]:


len(seleccion['idCliente'].unique()) >= len(cliente['idCliente'].unique())


# ### Asignación de valores en función de otra/s columna/s

# In[30]:


cliente['nuevacolumna'] = "Mujer"


# In[31]:


filtro = cliente['Sexo'] == 'H'
cliente.loc [filtro, 'nuevacolumna'] = 'Hombre'
cliente [['Sexo','nuevacolumna']]


# Dividimos el dataframe en 2 en función de un criterio.  En este caso vamos a dividirlo en base al sexo... (otro método)

# In[32]:


hombres, mujeres = cliente [filtro], cliente [-filtro]
print (hombres.Sexo.value_counts())
print (mujeres.Sexo.value_counts())


# Lo complicamos un poco más, cogiendo una columna con más de 2 ocurrencias.  En este caso, dividimos los datos por Comunidad

# In[33]:


datosPorComunidad = {}

for a in cliente['Comunidad'].unique():

    datosPorComunidad[a] = cliente[cliente['Comunidad'] == a]


# Ahora tenemos tantos diccionarios como comunidades tenemos en nuestros datos.  Para extraer cualquiera de dichos diccionarios...

# In[34]:


datosPorComunidad ['País Vasco'].head()


# In[35]:


datosPorComunidad ['Cataluña'].head()


# In[36]:


# Los datos segmentados están en formato diccionario
type(datosPorComunidad)


# In[37]:


# Podemos convertir los datos a dataframe
PaisVasco = pd.DataFrame(datosPorComunidad['País Vasco'])


# In[38]:


PaisVasco.head()


# Otra manera de segmentar los datos, en este caso agruparemos los datos a través de una columna (y obtendremos un dataframe)

# In[39]:


datosPorComunidad = cliente.groupby(cliente['Comunidad'])


# In[40]:


datosPorComunidad.get_group("País Vasco")


# In[41]:


# Los datos segmentados están en formato dataframe
type(datosPorComunidad)


# Ahora que tenemos los datos segmentados, podríamos guardar las tablas de manera independiente.

# In[42]:


comunidades = cliente.Comunidad.unique()
comunidades


# In[43]:


for a in comunidades:
    try:
        tmp = datosPorComunidad.get_group(a)
        mujeres = tmp['Sexo'].value_counts()[1]
        hombres = tmp['Sexo'].value_counts()[0]
        print ("En la comunidad ", a, " el total de mujeres es ", mujeres, " y el de hombres es ", hombres)
        nombre = './data/' + 'datos '+ a + '.csv'
        tmp.to_csv(nombre, index=False)
    except:
        continue


# ## Transformaciones por columnas

# In[44]:


cliente = pd.read_csv('./data/dimension_cliente.csv', sep='\t', encoding='UTF-8')


# In[45]:


# Unimos columnas
cliente['Apellidos'] = cliente['Apellido1'] + ", " + cliente ['Apellido2']
cliente.head()


# In[46]:


# Reordenamos las columnas (colocamos Apellidos junto a Apellido1 y Apellido2)
columnas = list(cliente.columns)
orden = columnas [0:5] + [columnas [-1]] + columnas [5:-1] # Ojo, el segundo elemento de la lista es un str por eso lo convierto en lista antes de añadirlo a orden
orden


# In[47]:


cliente = cliente.reindex(columns = orden)


# In[48]:


cliente.head()


# In[49]:


# Dividimos el campo mail a través de la arroba
cliente['Nick'] = cliente ['Correo'].str.split('@', expand=True)[0]
cliente['Dominio'] = cliente ['Correo'].str.split('@', expand=True)[1]


# In[50]:


cliente.head()


# In[51]:


# Vuelvo a reordenar columnas...
columnas = list(cliente.columns)
orden = columnas [0:6] + columnas [-2:] + columnas [6:-2]
cliente = cliente.reindex(columns = orden)


# In[52]:


cliente.head()


# ## Expresiones regulares

# In[53]:


cliente = pd.read_csv('./data/dimension_cliente.csv', sep='\t', encoding='UTF-8')


# In[54]:


# Clientes cuyo NIF tiene un 2
filtro = cliente['NIF'].str.contains('2')
cliente ['TienenUn2'] = filtro


# In[55]:


# Clientes cuyo NIF empieza por 2
filtro = cliente['NIF'].str.contains('^2')
cliente ['EmpiezaPor2'] = filtro


# In[56]:


# Clientes cuyo NIF tiene 9 elementos (longitud 9)
filtro = cliente['NIF'].str.contains('\w{9}')
cliente ['9Elementos'] = filtro


# In[57]:


# Clientes cuyo NIF tiene el formato correcto
filtro = cliente['NIF'].str.contains('\d{8}\w{1}')
cliente ['DniCorrecto'] = filtro


# In[58]:


cliente


# In[59]:


# Eliminamos espacios y - de la columna NIF y volvemos a comprobar si los NIF correctos 
cliente['NIF'] = cliente ['NIF'].str.replace("-","").str.replace(" ","")


# In[60]:


# Clientes cuyo NIF tiene el formato correcto
filtro = cliente['NIF'].str.contains('\d{8}\w{1}')
cliente ['DniCorrecto2'] = filtro


# In[61]:


cliente


# ## El formato fecha

# #### Trabajaremos ahora con datos tipo fecha

# In[62]:


from datetime import datetime


# In[63]:


df = pd.DataFrame({'fecha original': ['1/8/2021 20:30:00',
                                      '15/9/2021 15:15:13',
                                      '3/10/2021 08:10:02', 
                                      '3/11/2021 23:59:59', 
                                      '3/12/2021 00:00:01'],
                   'valor': [6,5, 2, 3, 4]})


# In[64]:


df.info()


# In[65]:


# Convertir string a datetime
df['fecha1'] = pd.to_datetime(df['fecha original'])
df.info()


# #### Por defecto Python parsea los datos con el mes primero (MM/DD, MM DD, MM-DD)(formato americano).  Para utilizar el formato europeo, añadimos el argumento dayfirst.

# In[66]:


# Convertir string a datetime 2
df['fecha2'] = pd.to_datetime(df['fecha original'], dayfirst=True) # de esta manera procesamos Año - Mes - Día


# In[67]:


df


# #### Usando máscaras nos saltamos el problema del formato para fecha que por defecto usa Python, y podemos especificarle  

# In[68]:


# Usando máscaras...
df['fecha3'] = pd.to_datetime(df['fecha original'], format= '%d/%m/%Y %H:%M:%S')


# In[69]:


# Eliminar el timestamp...
df['fecha4'] = pd.to_datetime(df['fecha original']).dt.date


# In[70]:


df


# In[71]:


# Extraer la hora de timestamp de los datos
df['hora'] = pd.to_datetime(df['fecha original']).dt.time


# In[72]:


df


# #### Podemos pedirle a python que infiera el formato de fecha de los datos

# In[73]:


pd.to_datetime(df['fecha original'], infer_datetime_format=True)


# #### to_datetime() incluye un argumento para tratar los casos en los que se produce un error de conversión.  Los valores posibles son, raise, coerce, ignore.  Por defecto toma raise.

# In[74]:


pd.to_datetime(df['fecha original'], errors='ignore')


# #### Construcción de una variable fecha a partir de otras columnas

# In[75]:


# Extraemos primero los datos de día, mes y año para poder reconstruirlos luego
df['dia'] = df['fecha1'].dt.day
df['mes'] = df['fecha1'].dt.month
df['año'] = df['fecha1'].dt.year


# In[76]:


df.head()


# In[77]:


# Lo hacemos por pasos para ver el proceso
construccionFecha = df.dia.astype('string') + '/' + df.mes.astype('string') + '/' + df['año'].astype('string') 
df['fecha5'] = pd.to_datetime(construccionFecha)


# In[78]:


df['fecha5']


# #### Otras opciones interesantes:

# In[79]:


# Extracción del número de semana
df['numSemana'] = df['fecha1'].dt.week


# In[80]:


# Extracción del número de día de la semana
df['diadelasemana'] = df['fecha1'].dt.dayofweek


# In[81]:


# Comprobación de año bisiesto
df['bisiesto'] = df['fecha1'].dt.is_leap_year


# In[82]:


df


# In[83]:


# Ahora que tenemos el día de la semana, podemos 'mapear' ese valor a una etiqueta
mapeodiassemana = {
    0: 'Lunes',
    1: 'Martes',
    2: 'Miércoles',
    3: 'Jueves',
    4: 'Viernes',
    5: 'Sábado',
    6: 'Domingo'
}
df['nombrediadelasemana'] = df['diadelasemana'].map(mapeodiassemana)
df


# #### Calcular los días desde una fecha hasta el día actual.

# In[84]:


hoy = pd.to_datetime('today')


# In[85]:


df['totaldias'] = hoy - df['fecha1']


# In[86]:


# Si quisieramos ver por ejemplo la diferencia entre años
df['totalaños'] = hoy.year - df['fecha1'].dt.year


# In[87]:


df


# #### Cuando trabajamos con datos con marca temporal, puede ser interesante usar la fecha como índice.  De esta manera podremos trabajar con los datos de manera más eficiente.

# In[132]:


df = df.set_index('fecha1')


# In[133]:


df


# In[134]:


# Si ahora quisiéramos obtener los datos del 2021
df.loc ['2021']


# In[137]:


df.loc ['2021-01']


# In[147]:


# Podemos filtrar por rangos de fechas...  aquí filtramos todos los registros del mes de enero 2021 entre el 1 y el 10 hasta el 3 de octubre de 2021
df.loc ['2021-1 1 10':'2021-3-10']


# ## Discretización de variables

# In[212]:


clima = pd.read_csv('./data/clima.csv', sep=";", decimal=",", encoding='UTF-8')


# In[153]:


clima


# In[213]:


# Simplifico los nombres de columna
clima.columns = ['fecha','temperatura','humedad','precipitaciones','viento']


# ### Las 3 maneras más habituales de segmentar son por: Intervalo, frecuencia y cluster.
# 
# ### **Segmentación por intervalo**
# ### En la segmentación por intervalo, partimos el rango de datos en n partes iguales. El problema principal se encuentra en los Outliers.  Si medimos salarios y resulta que  el 99% de la gente tiene salarios sobre 1000€ y el 1% tiene el salario de Amancio Ortega. Los intervalos tendrán la misma amplitud, pero los outliers estarán en el mismo intervalo o valor.
# 
# ### **Segmentación por frecuencia.**
# ### Cada segmento contiene la misma cantidad de datos.  El rango de cada segmento es variable
# 
# ### **Segmentación por cluster**
# ### Aquí se parte por clusters, busca en los datos cuantos grupos hay.  La herramienta a usar es kmeans (K-Medias)

# In[ ]:


from sklearn.preproccesing import KBin


# In[251]:


# Dicretización por intervalo:  Discretizamos en 4 intervalos iguales (en rango de datos)
clima['hum_int'] = pd.cut(clima['humedad'], 4)


# In[207]:


clima


# In[215]:


# Un caso específico sería hacer una discretización en base a los intervalos que queramos. Me voy a basar en los cuartiles para ello.
clima.humedad.describe()


# In[216]:


clima['hum_int2'] = pd.cut(clima['humedad'], bins=[0,26,74,80,98])


# In[221]:


clima


# # Probad vosotros a discretizar la variable precipitaciones, usando los mismos cortes que en este ejemplo.  ¿Qué sucede?
# Mirar la ayuda de pandas
# https://pandas.pydata.org/docs/reference/api/pandas.cut.html

# In[223]:


# Incluso añadir etiquetas a los intervalos
clima['hum_int2'] = pd.cut(clima['humedad'], bins=[0,26,74,80,98], include_lowest=True, labels=['1er Int','2do Int', '3er Int', '4o Int'])


# In[232]:


# Dicretización por frecuencia:  Discretizamos en 4 intervalos iguales (en rango de datos).  Este caso coje los cuartiles que obtenemos en .describe.  q puede coger el valor que queramos.
clima['hum_frec'] = pd.qcut(clima['humedad'], q=4)


# In[231]:


clima


# In[252]:


print (pd.value_counts(clima.hum_int))
print (pd.value_counts(clima.hum_int2))
print (pd.value_counts(clima.hum_frec))


# In[ ]:





# 3 tipos de discretización:
# * Intervalo (igual ancho para todos los segmentos)
# * Frecuencia (igual frecuencia de aparición de elementos en los segmentos)
# * Cluster (discretización en base al algoritmo KMeans)

# In[ ]:


get_ipython().system('pip install feature_engine')


# In[ ]:


from sklearn.preprocessing import KBinsDiscretizer
from feature_engine.discretisation import EqualWidthDiscretiser


# In[ ]:


clima = pd.read_csv('./data/clima.csv', sep=';', decimal=',')
clima


# In[ ]:


intervalo = EqualWidthDiscretiser(bins=3, variables=['Precipitaciones'])


# In[ ]:


intervalo.fit(clima)
transformado = intervalo.transform(clima)


# In[ ]:


transformado

