Microproyecto 4, Inteligencia Artificial:

Árbol de decisión y clasificación bayesiana para predecir ingresos superiores a 50.000 US

Integrantes del equipo:

* Alejandro Mejía

* Cristian Tamayo

* Juan G Sarrias

Nov. 2020

P1. Cargamos librerías

Las librerías necesarias serán pandas y numpy para el manejo de los datos, matplot para gráficas y sklearn tanto para el análisis del árbol como para la clasificación bayesiana.

In [151]:
# Imports necesarios para graficas
import numpy as np
import pandas as pd
import seaborn as sb
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = (16, 9)
plt.style.use('ggplot')

# Imports necesarios para arbol de decision
from sklearn import tree
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from IPython.display import Image as PImage
from subprocess import check_call
from PIL import Image, ImageDraw, ImageFont

# Imports necesarios para clasificacion bayesiana

from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.naive_bayes import GaussianNB
from sklearn.feature_selection import SelectKBest

P2. Importamos los datos.

El dataframe no presenta nombres en las columnas, como tal se busco la fuente del dataset y se agregan los nombres de las columnas

Fuente: https://archive.ics.uci.edu/ml/datasets/adult

In [152]:
#Se importan datos de desempleo desde el PC a Colaboratory
from google.colab import files
import io
uploaded = files.upload()
adult_dataframe_leido = pd.read_csv(io.BytesIO(uploaded['adult.data']), names= ["age", "workclass", "fnlwgt", "education", "education-num", "marital-status", "occupation", "relationship", "race", "sex", "capital-gain", "capital-loss", "hours-per-week", "native-country", "income"] )
adult_dataframe_leido.head()
Upload widget is only available when the cell has been executed in the current browser session. Please rerun this cell to enable.
Saving adult.data to adult (1).data
Out[152]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black Female 0 0 40 Cuba <=50K

P2.1. Cuantos datos son:

In [153]:
adult_dataframe_leido.shape
Out[153]:
(32561, 15)

Son 32561 datos con 15 atributos

P2.2. ¿Cuantos tienen ingresos mayores a $50000 US al año?

In [154]:
adult_dataframe_leido.groupby('income').size()
Out[154]:
income
 <=50K    24720
 >50K      7841
dtype: int64
In [155]:
sb.factorplot('income',data=adult_dataframe_leido,kind="count")
/usr/local/lib/python3.6/dist-packages/seaborn/categorical.py:3704: UserWarning: The `factorplot` function has been renamed to `catplot`. The original name will be removed in a future release. Please update your code. Note that the default `kind` in `factorplot` (`'point'`) has changed `'strip'` in `catplot`.
  warnings.warn(msg)
/usr/local/lib/python3.6/dist-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variable as a keyword arg: x. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
  FutureWarning
Out[155]:
<seaborn.axisgrid.FacetGrid at 0x7fd60b518320>

Son 7841 personas que tienen ingreso mayores a $50000 US al año

P3. Pre-Procesamiento de los datos:

P3.1. Buscamos si hay alguna relación evidente entre edad y horas por semana

In [156]:
colores= {" <=50K":'orange',
          " >50K": 'blue'}
tamanios=[60,40]

f1 = adult_dataframe_leido['age'].values
f2 = adult_dataframe_leido['hours-per-week'].values

asignar=[]
for index, row in adult_dataframe_leido.iterrows():
  asignar.append(colores[row['income']])

plt.figure(figsize=(16,10))
plt.scatter(f1, f2, c=asignar, s=30)
#plt.axis([1960,2005,0,600])
plt.show()

No se visualiza una correlación entre estos datos

P3.2. Buscamos relación entre edad y ganacia de capital:

In [157]:
f1 = adult_dataframe_leido['age'].values
f2 = adult_dataframe_leido['capital-gain'].values

asignar=[]
for index, row in adult_dataframe_leido.iterrows():
  asignar.append(colores[row['income']])
  
plt.figure(figsize=(16,10))
plt.scatter(f1, f2, c=asignar, s=30)
#plt.axis([1960,2005,0,600])
plt.show()

No se nota una correlación

P3.3. Se verifican filas que tengan datos vacíos:

In [ ]:
adult_dataframe_leido[adult_dataframe_leido['age'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['workclass'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['fnlwgt'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['education'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['education-num'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['marital-status'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['occupation'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['relationship'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['race'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['sex'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['capital-gain'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['capital-loss'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['hours-per-week'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['native-country'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
In [ ]:
adult_dataframe_leido[adult_dataframe_leido['income'].isnull()==True]
Out[ ]:
age workclass fnlwgt education education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income

P3.4. Mapeo de atributos:

Se contarán la cantidad de filas que tiene cada valor dentro de los datos que no son numericos y se darán intervalos para los que son de tipo contínuo.

In [158]:
separador = "### ### ###"
grouped11 = adult_dataframe_leido.groupby('workclass').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por clase de trabajo:")
print(neworder11)
print(separador)
grouped11 = adult_dataframe_leido.groupby('education').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por nivel de eduación:")
print(neworder11)
print(separador)
grouped11 = adult_dataframe_leido.groupby('marital-status').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por estado civil:")
print(neworder11)
print(separador)
grouped11 = adult_dataframe_leido.groupby('occupation').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por ocupación:")
print(neworder11)
print(separador)
grouped11 = adult_dataframe_leido.groupby('relationship').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas relación:")
print(neworder11)
print(separador)
grouped11 = adult_dataframe_leido.groupby('race').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por raza:")
print(neworder11)
print(separador)
grouped11 = adult_dataframe_leido.groupby('sex').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por sexo:")
print(neworder11)
print(separador)
grouped11 = adult_dataframe_leido.groupby('native-country').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por país de origen:")
print(neworder11)
print(separador)
 Cantidad de personas por clase de trabajo:
workclass
 Private             22696
 Self-emp-not-inc     2541
 Local-gov            2093
 ?                    1836
 State-gov            1298
 Self-emp-inc         1116
 Federal-gov           960
 Without-pay            14
 Never-worked            7
dtype: int64
### ### ###
 Cantidad de personas por nivel de eduación:
education
 HS-grad         10501
 Some-college     7291
 Bachelors        5355
 Masters          1723
 Assoc-voc        1382
 11th             1175
 Assoc-acdm       1067
 10th              933
 7th-8th           646
 Prof-school       576
 9th               514
 12th              433
 Doctorate         413
 5th-6th           333
 1st-4th           168
 Preschool          51
dtype: int64
### ### ###
 Cantidad de personas por estado civil:
marital-status
 Married-civ-spouse       14976
 Never-married            10683
 Divorced                  4443
 Separated                 1025
 Widowed                    993
 Married-spouse-absent      418
 Married-AF-spouse           23
dtype: int64
### ### ###
 Cantidad de personas por ocupación:
occupation
 Prof-specialty       4140
 Craft-repair         4099
 Exec-managerial      4066
 Adm-clerical         3770
 Sales                3650
 Other-service        3295
 Machine-op-inspct    2002
 ?                    1843
 Transport-moving     1597
 Handlers-cleaners    1370
 Farming-fishing       994
 Tech-support          928
 Protective-serv       649
 Priv-house-serv       149
 Armed-Forces            9
dtype: int64
### ### ###
 Cantidad de personas relación:
relationship
 Husband           13193
 Not-in-family      8305
 Own-child          5068
 Unmarried          3446
 Wife               1568
 Other-relative      981
dtype: int64
### ### ###
 Cantidad de personas por raza:
race
 White                 27816
 Black                  3124
 Asian-Pac-Islander     1039
 Amer-Indian-Eskimo      311
 Other                   271
dtype: int64
### ### ###
 Cantidad de personas por sexo:
sex
 Male      21790
 Female    10771
dtype: int64
### ### ###
 Cantidad de personas por país de origen:
native-country
 United-States                 29170
 Mexico                          643
 ?                               583
 Philippines                     198
 Germany                         137
 Canada                          121
 Puerto-Rico                     114
 El-Salvador                     106
 India                           100
 Cuba                             95
 England                          90
 Jamaica                          81
 South                            80
 China                            75
 Italy                            73
 Dominican-Republic               70
 Vietnam                          67
 Guatemala                        64
 Japan                            62
 Poland                           60
 Columbia                         59
 Taiwan                           51
 Haiti                            44
 Iran                             43
 Portugal                         37
 Nicaragua                        34
 Peru                             31
 Greece                           29
 France                           29
 Ecuador                          28
 Ireland                          24
 Hong                             20
 Cambodia                         19
 Trinadad&Tobago                  19
 Laos                             18
 Thailand                         18
 Yugoslavia                       16
 Outlying-US(Guam-USVI-etc)       14
 Honduras                         13
 Hungary                          13
 Scotland                         12
 Holand-Netherlands                1
dtype: int64
### ### ###

P3.5. Para datos "?":

Para los valores que se encuentran como "?", se incluirán generalmente con el valor asignado a "otros".

P3.6. Para tipo de Trabajo:

Todos los de gobierno se tomarán del mismo tipo,

quedaría definido asi:

  • 0 = gobierno
  • 1 = privado
  • 2 = independiente sin ingresos
  • 3 = independiente con ingresos
  • 4 = otros
In [161]:
dict = {
    " Private": 1,
    " Self-emp-not-inc": 2, 
    " Local-gov": 0, 
    " ?": 4, 
    " State-gov": 0, 
    " Self-emp-inc": 3, 
    " Federal-gov": 0, 
    " Without-pay": 4, 
    " Never-worked": 4,
    " " : 0
    }
adult_dataframe_leido['workclassEncoded'] = adult_dataframe_leido['workclass'].map(dict).astype(int)

P3.7. Para estado civil:

Casados se incluyen todos en el mismo,

  • 0 = Casado
  • 1 = Nunca Casado
  • 2 = Divorciado
  • 3 = Separado
  • 4 = Enviudado
In [164]:
adult_dataframe_leido['marital-statusEncoded'] = adult_dataframe_leido['marital-status'].map( {
    ' Married-civ-spouse': 0, 
    ' Never-married': 1, 
    ' Divorced': 2, 
    ' Separated': 3, 
    ' Widowed': 4,
    ' Married-spouse-absent': 0,
    ' Married-AF-spouse': 0 
}).astype(int)

P3.8. Para Ocupación:

solo se agrupo fuerzas armadas con servicios de protección. Los otros valores ya estaban lo suficientemente segmentados

  • Prof-specialty = 0
  • Craft-repair = 1
  • Exec-managerial = 2
  • Adm-clerical = 3
  • Sales = 4
  • Other-service = 5
  • Machine-op-inspct = 6
  • Transport-moving = 7
  • Handlers-cleaners = 8
  • Farming-fishing = 9
  • Tech-support = 10
  • Protective-serv = 11
  • Priv-house-serv = 12
  • Armed-Forces = 11
  • ? = 13
In [167]:
adult_dataframe_leido['occupationEncoded'] = adult_dataframe_leido['occupation'].map( {
    ' Prof-specialty' : 0,       
    ' Craft-repair' : 1  ,      
    ' Exec-managerial' : 2  ,    
    ' Adm-clerical' : 3 ,        
    ' Sales' : 4  ,              
    ' Other-service' : 5  ,      
    ' Machine-op-inspct' : 6   , 
    ' ?' : 13     ,               
    ' Transport-moving' : 7   ,  
    ' Handlers-cleaners' : 8 ,   
    ' Farming-fishing' : 9   ,   
    ' Tech-support' : 10    ,     
    ' Protective-serv' : 11  ,    
    ' Priv-house-serv' : 12  ,    
    ' Armed-Forces' : 11       
    } ).astype(int)

P3.9. Para relación:

Ya estan lo suficientemente segmentado.

  • No en familia: 0
  • Esposo : 1
  • Esposa: 2
  • Tiene un hijo : 3
  • No casado : 4
  • Otro familiar : 5
In [170]:
adult_dataframe_leido['relationshipEncoded'] = adult_dataframe_leido['relationship'].map( {
    ' Husband' : 1,
    ' Not-in-family' : 0,
    ' Own-child': 3,
    ' Unmarried': 4,
    ' Wife': 2,
    ' Other-relative': 5,
    '': 0} ).astype(int)

P3.10. Para raza:

Ya esta lo suficientemente segmentado.

  • Blanco : 0
  • Negro : 1
  • Asiatico, Pacifico; Isleño : 2
  • Americano-Indio-Esquimal : 3
  • Otro : 4
In [171]:
adult_dataframe_leido['raceEncoded'] = adult_dataframe_leido['race'].map( {
    ' White' : 0,
    ' Black' : 1,
    ' Asian-Pac-Islander': 2,
    ' Amer-Indian-Eskimo': 3,
    ' Other': 4} ).astype(int)

P3.11. Para sexo:

Ya esta lo suficientemente segmentado.

  • Hombre : 0
  • Mujer : 1
In [172]:
adult_dataframe_leido['sexEncoded'] = adult_dataframe_leido['sex'].map( {
    ' Male' : 0,
    ' Female' : 1,
}).astype(int)

P3.12. Para Paises:

Se clasificaran por continente, exceptuando a Estados Unidos porque tiene mas del 80% de los datos en el dataframe.

  • Estados Unidos y sus islas : 1
  • Norteamérica (sin Estados Unidos) : 2
  • Europa : 3
  • Asia : 4
  • Oceanía : 5
  • Sudamérica : 6
  • Centroamérica e Islas del Caribe : 7
  • Otros : 8
In [173]:
adult_dataframe_leido['native-countryEncoded'] = adult_dataframe_leido['native-country'].map( {
    ' United-States': 1, 
    ' Mexico': 2, 
    ' Canada': 2, 
    ' ?': 8, 
    ' South': 8, 
    ' Philippines': 4, 
    ' Germany': 3, 
    ' Puerto-Rico': 7, 
    ' El-Salvador': 7, 
    ' India': 4, 
    ' Cuba': 7, 
    ' England': 3, 
    ' Jamaica': 7, 
    ' China': 4, 
    ' Italy': 3, 
    ' Dominican-Republic': 7, 
    ' Vietnam': 4, 
    ' Guatemala': 7, 
    ' Japan': 4, 
    ' Poland': 3,
    ' Taiwan': 4, 
    ' Columbia': 6, 
    ' Haiti': 7, 
    ' Iran': 4, 
    ' Portugal': 3, 
    ' Nicaragua': 7, 
    ' Peru': 6, 
    ' Greece': 3, 
    ' France': 3, 
    ' Ecuador': 6, 
    ' Ireland': 3, 
    ' Hong': 4, 
    ' Cambodia': 4, 
    ' Trinadad&Tobago': 7, 
    ' Laos': 4, 
    ' Thailand': 4, 
    ' Yugoslavia': 3, 
    ' Outlying-US(Guam-USVI-etc)': 1, 
    ' Honduras': 7, 
    ' Hungary': 3, 
    ' Scotland': 3, 
    ' Holand-Netherlands': 7, 
}).astype(int)

P3.13. Para el ingreso anual:

In [174]:
adult_dataframe_leido['incomeEncoded'] = adult_dataframe_leido['income'].map( {
    ' <=50K' : 0,
    ' >50K' : 1,
}).astype(int)

Observación:

La columna education-num representa los valores numericos de la columna education. Se verificará por las cantidades de datos con el mismo valor.

In [175]:
grouped11 = adult_dataframe_leido.groupby('education').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por nivel de educación:")
print(neworder11)
grouped11 = adult_dataframe_leido.groupby('education-num').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por numero de educación:")
print(neworder11)
 Cantidad de personas por nivel de educación:
education
 HS-grad         10501
 Some-college     7291
 Bachelors        5355
 Masters          1723
 Assoc-voc        1382
 11th             1175
 Assoc-acdm       1067
 10th              933
 7th-8th           646
 Prof-school       576
 9th               514
 12th              433
 Doctorate         413
 5th-6th           333
 1st-4th           168
 Preschool          51
dtype: int64
 Cantidad de personas por numero de educación:
education-num
9     10501
10     7291
13     5355
14     1723
11     1382
7      1175
12     1067
6       933
4       646
15      576
5       514
8       433
16      413
3       333
2       168
1        51
dtype: int64

P3.14. Para el nivel educativo:

Como las cantidades son iguales para cada tipo de dato y esta probabilidad es mínima para tal cantidad de datos, serán asumidos que representan lo mismo, solo que education-num es un mapeo de los valores de education a valores númericos

Ahora mapeamos las otras columnas en intervalos:

P3.15. Mapeo de edad por décadas:

In [176]:
# Mapping Age por decadas
adult_dataframe_age = adult_dataframe_leido['age']
adult_dataframe_leido.loc[ adult_dataframe_age < 10, 'ageEncoded']									 = 0
adult_dataframe_leido.loc[(adult_dataframe_age < 20) & (adult_dataframe_age >= 10), 'ageEncoded'] = 1
adult_dataframe_leido.loc[(adult_dataframe_age < 30) & (adult_dataframe_age >= 20), 'ageEncoded'] = 2
adult_dataframe_leido.loc[(adult_dataframe_age < 40) & (adult_dataframe_age >= 30), 'ageEncoded'] = 3
adult_dataframe_leido.loc[(adult_dataframe_age < 50) & (adult_dataframe_age >= 40), 'ageEncoded'] = 4
adult_dataframe_leido.loc[(adult_dataframe_age < 60) & (adult_dataframe_age >= 50), 'ageEncoded'] = 5
adult_dataframe_leido.loc[(adult_dataframe_age < 70) & (adult_dataframe_age >= 60), 'ageEncoded'] = 6
adult_dataframe_leido.loc[(adult_dataframe_age < 80) & (adult_dataframe_age >= 70), 'ageEncoded'] = 7
adult_dataframe_leido.loc[(adult_dataframe_age < 90) & (adult_dataframe_age >= 80), 'ageEncoded'] = 8
adult_dataframe_leido.loc[(adult_dataframe_age < 100) & (adult_dataframe_age >= 90), 'ageEncoded'] = 9	
#adult_dataframe_leido.astype({"ageEncoded":int})

P3.16. Mapeo de capital-gain por intervalos:

In [177]:
#EL MAXIMO DE CAPITAL-GAIN
adult_dataframe_leido["capital-gain"].max()
Out[177]:
99999
In [178]:
# Mapping capital-gain de a intervalos de 10000
adult_dataframe_capital = adult_dataframe_leido['capital-gain']
adult_dataframe_leido.loc[ adult_dataframe_capital < 10000, 'capital-gainEncoded']									 = 0
adult_dataframe_leido.loc[(adult_dataframe_capital < 20000) & (adult_dataframe_capital >= 10000), 'capital-gainEncoded'] = 1
adult_dataframe_leido.loc[(adult_dataframe_capital < 30000) & (adult_dataframe_capital >= 20000), 'capital-gainEncoded'] = 2
adult_dataframe_leido.loc[(adult_dataframe_capital >= 30000), 'capital-gainEncoded'] = 3
#adult_dataframe_leido.astype({"capital-gainEncoded":int})

P3.17. Mapeo de capital-loss por intervalos:

In [179]:
#EL MAXIMO DE CAPITAL-Loss
adult_dataframe_leido["capital-loss"].max()
Out[179]:
4356
In [180]:
# Mapping capital-loss de a intervalos de 1000
adult_dataframe_capital = adult_dataframe_leido['capital-loss']
adult_dataframe_leido.loc[ adult_dataframe_capital < 1000, 'capital-lossEncoded']									 = 0
adult_dataframe_leido.loc[(adult_dataframe_capital < 2000) & (adult_dataframe_capital >= 1000), 'capital-lossEncoded'] = 1
adult_dataframe_leido.loc[(adult_dataframe_capital >= 2000), 'capital-lossEncoded'] = 2
#adult_dataframe_leido.astype({"capital-lossEncoded":int})

P3.18. Mapeo de horas trabajadas por semana:

In [181]:
#EL MAXIMO DE Hours-Per_Week
adult_dataframe_leido["hours-per-week"].max()
Out[181]:
99
In [182]:
# Mapping hpurs-per-week de a intervalos de 10
adult_dataframe_hours = adult_dataframe_leido['hours-per-week']
adult_dataframe_leido.loc[ adult_dataframe_hours < 10, 'hours-per-weekEncoded']									 = 0
adult_dataframe_leido.loc[(adult_dataframe_hours < 20) & (adult_dataframe_hours >= 10), 'hours-per-weekEncoded'] = 1
adult_dataframe_leido.loc[(adult_dataframe_hours < 30) & (adult_dataframe_hours >= 20), 'hours-per-weekEncoded'] = 2
adult_dataframe_leido.loc[(adult_dataframe_hours < 40) & (adult_dataframe_hours >= 30), 'hours-per-weekEncoded'] = 3
adult_dataframe_leido.loc[(adult_dataframe_hours < 50) & (adult_dataframe_hours >= 40), 'hours-per-weekEncoded'] = 4
adult_dataframe_leido.loc[(adult_dataframe_hours < 60) & (adult_dataframe_hours >= 50), 'hours-per-weekEncoded'] = 5
adult_dataframe_leido.loc[(adult_dataframe_hours < 70) & (adult_dataframe_hours >= 60), 'hours-per-weekEncoded'] = 6
adult_dataframe_leido.loc[(adult_dataframe_hours < 80) & (adult_dataframe_hours >= 70), 'hours-per-weekEncoded'] = 7
adult_dataframe_leido.loc[(adult_dataframe_hours < 90) & (adult_dataframe_hours >= 80), 'hours-per-weekEncoded'] = 8
adult_dataframe_leido.loc[(adult_dataframe_hours < 100) & (adult_dataframe_hours >= 90), 'hours-per-weekEncoded'] = 9	
#adult_dataframe_leido.astype({"hours-per-weekEncoded":int})

P3.19. Mapeo de FNLWGT:

La variable continua fnlwgt representa el peso final, que es el número de unidades en la población objetivo que representa la unidad que responde.

En otras palabras, esta es la cantidad de personas que el censo cree la entrada representa .

In [183]:
#EL MAXIMO DE fnlwgt
adult_dataframe_leido["fnlwgt"].max()
Out[183]:
1484705
In [184]:
# Mapping hpurs-per-week de a intervalos de 100000
adult_dataframe_fnl = adult_dataframe_leido['fnlwgt']
adult_dataframe_leido.loc[ adult_dataframe_fnl < 100000, 'fnlwgtEncoded']									 = 0
adult_dataframe_leido.loc[(adult_dataframe_fnl < 200000) & (adult_dataframe_fnl >= 100000), 'fnlwgtEncoded'] = 1
adult_dataframe_leido.loc[(adult_dataframe_fnl < 300000) & (adult_dataframe_fnl >= 200000), 'fnlwgtEncoded'] = 2
adult_dataframe_leido.loc[(adult_dataframe_fnl < 400000) & (adult_dataframe_fnl >= 300000), 'fnlwgtEncoded'] = 3
adult_dataframe_leido.loc[(adult_dataframe_fnl < 500000) & (adult_dataframe_fnl >= 400000), 'fnlwgtEncoded'] = 4
adult_dataframe_leido.loc[(adult_dataframe_fnl < 600000) & (adult_dataframe_fnl >= 500000), 'fnlwgtEncoded'] = 5
adult_dataframe_leido.loc[(adult_dataframe_fnl >= 600000), 'fnlwgtEncoded'] = 6
#adult_dataframe_leido.astype({"hours-per-weekEncoded":int})

P3.20. Se eliminan las columnas antiguas y se muestran las clasificaciones totales anteriores:

In [185]:
drop_elements = ['age',
                 'workclass',
                 'fnlwgt',
                 'education',
                 'marital-status',
                 'occupation',
                 'relationship',
                 'race',
                 'sex',
                 'capital-gain',
                 'capital-loss',
                 'hours-per-week',
                 'native-country',
                 'income'
                ]
adult_dataframe_encoded = adult_dataframe_leido.drop(drop_elements, axis = 1)
In [186]:
##Verificamos el dataframe mapeado
adult_dataframe_encoded
Out[186]:
education-num workclassEncoded marital-statusEncoded occupationEncoded relationshipEncoded raceEncoded sexEncoded native-countryEncoded incomeEncoded ageEncoded capital-gainEncoded capital-lossEncoded hours-per-weekEncoded fnlwgtEncoded
0 13 0 1 3 0 0 0 1 0 3.0 0.0 0.0 4.0 0.0
1 13 2 0 2 1 0 0 1 0 5.0 0.0 0.0 1.0 0.0
2 9 1 2 8 0 0 0 1 0 3.0 0.0 0.0 4.0 2.0
3 7 1 0 8 1 1 0 1 0 5.0 0.0 0.0 4.0 2.0
4 13 1 0 0 2 1 1 7 0 2.0 0.0 0.0 4.0 3.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
32556 12 1 0 10 2 0 1 1 0 2.0 0.0 0.0 3.0 2.0
32557 9 1 0 6 1 0 0 1 1 4.0 0.0 0.0 4.0 1.0
32558 9 1 4 3 4 0 1 1 0 5.0 0.0 0.0 4.0 1.0
32559 9 1 1 3 3 0 0 1 0 2.0 0.0 0.0 2.0 2.0
32560 9 3 0 2 2 0 1 1 1 5.0 1.0 0.0 4.0 2.0

32561 rows × 14 columns

P4. Se busca correlación:

Primero se muestra una descripción breve de los datos:

In [187]:
#verificamos dimensiones
adult_dataframe_encoded.shape
Out[187]:
(32561, 14)
In [188]:
#Verifiquemos estadisticos
adult_dataframe_encoded.describe()
Out[188]:
education-num workclassEncoded marital-statusEncoded occupationEncoded relationshipEncoded raceEncoded sexEncoded native-countryEncoded incomeEncoded ageEncoded capital-gainEncoded capital-lossEncoded hours-per-weekEncoded fnlwgtEncoded
count 32561.000000 32561.000000 32561.00000 32561.000000 32561.000000 32561.000000 32561.000000 32561.000000 32561.000000 32561.000000 32561.000000 32561.000000 32561.000000 32561.000000
mean 10.080679 1.184055 0.81742 4.298947 1.542397 0.221707 0.330795 1.395995 0.240810 3.410553 0.036516 0.055527 3.886214 1.396425
std 2.572720 0.910051 0.98844 3.579434 1.437431 0.627348 0.470506 1.383143 0.427581 1.399826 0.266678 0.269088 1.277708 1.057019
min 1.000000 0.000000 0.00000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000
25% 9.000000 1.000000 0.00000 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 2.000000 0.000000 0.000000 4.000000 1.000000
50% 10.000000 1.000000 1.00000 4.000000 1.000000 0.000000 0.000000 1.000000 0.000000 3.000000 0.000000 0.000000 4.000000 1.000000
75% 12.000000 1.000000 1.00000 6.000000 3.000000 0.000000 1.000000 1.000000 0.000000 4.000000 0.000000 0.000000 4.000000 2.000000
max 16.000000 4.000000 4.00000 13.000000 5.000000 4.000000 1.000000 8.000000 1.000000 9.000000 3.000000 2.000000 9.000000 6.000000

P4.1. Gráfica de correlación de Pearson:

In [189]:
colormap = plt.cm.viridis
plt.figure(figsize=(13,13))
plt.title('Pearson Correlation of Features')
sb.heatmap(adult_dataframe_encoded.astype(float).corr(),
           vmax=1.0,
           cmap=colormap,
           annot=True,
           linewidths=0.1,
           linecolor='white',
           square=True)
Out[189]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fd60b4a00f0>

P4.2. Variables usadas para clasificación:

Según la gráfica anterior, no hay variables muy correlacionadas (valores menores a 0.5), por lo que usarlas todas en el modelo aportará mejor información y más precisión al clasificar.

P5. Definición de nuestro Arbol de Decisión:

Se eligió una partición de datos del 80% para entrenamiento y 20% para testeo, esto debido a que hay más de 30000 datos y podemos tener un porcentaje "alto" de los datos para el testeo sin que afecte mucho al entrenamiento.

P5.1. Precisión obtenida para diferentes niveles de árbol:

In [190]:
cv = KFold(n_splits=5) # Numero deseado de "folds" que haremos
accuracies = list()
max_attributes = len(list(adult_dataframe_encoded))
depth_range = range(1, max_attributes + 1)

# Testearemos la profundidad de 1 a cantidad de atributos +1
for depth in depth_range:
    fold_accuracy = []
    tree_model = tree.DecisionTreeClassifier(criterion='entropy',
                                             min_samples_split=20,
                                             min_samples_leaf=5,
                                             max_depth = depth,
                                             class_weight={1:3.15}) # Por defecto todas las clases igual peso
                                                                   # Columna 1 (top) con peso de 3.15
    for train_fold, valid_fold in cv.split(adult_dataframe_encoded):
        f_train = adult_dataframe_encoded.loc[train_fold] 
        f_valid = adult_dataframe_encoded.loc[valid_fold] 

        model = tree_model.fit(X = f_train.drop(['incomeEncoded'], axis=1), 
                               y = f_train["incomeEncoded"]) 
        valid_acc = model.score(X = f_valid.drop(['incomeEncoded'], axis=1), 
                                y = f_valid["incomeEncoded"]) # calculamos la precision con el segmento de validacion
        fold_accuracy.append(valid_acc)

    avg = sum(fold_accuracy)/len(fold_accuracy)
    accuracies.append(avg)
    
# Mostramos los resultados obtenidos
df = pd.DataFrame({"Max Depth": depth_range, "Average Accuracy": accuracies})
df = df[["Max Depth", "Average Accuracy"]]
print(df.to_string(index=False))
 Max Depth  Average Accuracy
         1          0.699456
         2          0.701729
         3          0.747520
         4          0.775375
         5          0.770584
         6          0.771536
         7          0.769202
         8          0.777003
         9          0.774392
        10          0.778324
        11          0.778785
        12          0.780105
        13          0.783913
        14          0.780535

P5.2. Elección de profundidad:

Como se ve anteriormente, hasta el nivel 4, el árbol presentaba un crecimiento en la precisión.

A partir de ese nivel, la precisión oscila en valores cercanos a 0.7751 hasta el nivel 11 y subió muy levemente en el nivel 12, pero ese leve aumento no justifica elegir un nivel de profundidad tan alto ya que dicha precisión se debe al overfitting del algoritmo.

Por esta razón se eligió un nivel de profundidad de 4 y se considera más que suficiente para el conjunto de datos analizado.

P5.3. Elección de class weigth:

Se recuerda que la columna incomeEncoded está etiquetado con 0 para quienes tienen ingresos <=50K y 1 para quienes tienen >50K.

Con el class weigth se compensará de cierta manera el desbalance de los datos (incomeEncoded 0 [24720] dividido entre los 1 [7841] da 3.15).

In [191]:
grouped11 = adult_dataframe_leido.groupby('incomeEncoded').size()
neworder11 = grouped11.sort_values(ascending=False)
print(" Cantidad de personas por incomeEncoded:")
print(neworder11)
print(separador)
 Cantidad de personas por incomeEncoded:
incomeEncoded
0    24720
1     7841
dtype: int64
### ### ###

P5.4. Elección de mínimo de muestras por nodo y por hoja:

Por ensayo y error de diferentes valores, se observó que para valores de 20 muestras por nodo y 5 muestras por hoja, la precisión no variaba considerablemente por lo que al final se seleccionaron estos valores para el árbol.

P5.5. Elección de métrica y árbol:

Se seleccionó un árbol de clasificación, ya que las variables de entrada y la variable de salida son categóricas.

Para variables categóricas, se utiliza la métrica "entropía", por lo que fue la elegida para este conjunto de datos.

P6. Creación del árbol:

Como se vió anteriormente y luego de probar con diferentes métricas, se obtuvo que las mejores condiciones para la creación de un árbol de decisión con la mejor precisión para el problema son:

* Entropía como métrica elegida.

* Profundidad de 4

* min samples split de 20

* min samples leaf de 5

* class weigth de 3.15 para 1.

In [192]:
# Crear arrays de entrenamiento y las etiquetas que indican las ganancias mayores o menores a 50K
y_train = adult_dataframe_encoded['incomeEncoded']
x_train = adult_dataframe_encoded.drop(['incomeEncoded'], axis=1).values 

# Crear Arbol de decision con profundidad = 4
decision_tree = tree.DecisionTreeClassifier(criterion='entropy',
                                            min_samples_split=20,
                                            min_samples_leaf=5,
                                            max_depth = 4,
                                            class_weight={1:3.15})
decision_tree.fit(x_train, y_train)

# exportar el modelo a archivo .dot
with open(r"tree1.dot", 'w') as f:
     f = tree.export_graphviz(decision_tree,
                              out_file=f,
                              max_depth = 7,
                              impurity = True,
                              feature_names = list(adult_dataframe_encoded.drop(['incomeEncoded'], axis=1)),
                              class_names = [' <=50K', ' >50K'],
                              rounded = True,
                              filled= True )
        
# Convertir el archivo .dot a png para poder visualizarlo
check_call(['dot','-Tpng',r'tree1.dot','-o',r'tree1.png'])
PImage("tree1.png")
Out[192]:

P6.1. Precisión del árbol:

In [109]:
acc_decision_tree = round(decision_tree.score(x_train, y_train) * 100, 2)
print(acc_decision_tree,'%')
77.65 %

P6.2. Nodo Raíz:

El nodo raíz del árbol es "marital-statusEncoded", y fue elegido por el algoritmo automáticamente debido a que es el atributo con mayor ganancia debido a la alta entropía.

In [115]:
# Backup
adult_data = adult_dataframe_encoded.copy()
In [ ]:
# Restore
adult_dataframe_encoded = adult_data.copy()

P7. Clasificación Bayesiana:

Se creará a continuación el modelo de clasificación bayesiana.

P7.1. Elección de características:

Utilizando SelectKBest de SkLearn, se seleccionarán las 5 mejores características del dataframe y usaremos sólo esas para la clasificación bayesiana:

In [193]:
X=adult_dataframe_encoded.drop(['incomeEncoded'], axis=1)
y=adult_dataframe_encoded['incomeEncoded']

best=SelectKBest(k=5)
X_new = best.fit_transform(X, y)
selected = best.get_support(indices=True)
print(X.columns[selected])
X_new.shape
Index(['education-num', 'marital-statusEncoded', 'ageEncoded',
       'capital-gainEncoded', 'hours-per-weekEncoded'],
      dtype='object')
Out[193]:
(32561, 5)

P7.2. Nueva gráfica de correlación de Pearson:

Para las variables 'education-num', 'marital-statusEncoded', 'ageEncoded', 'capital-gainEncoded' y 'hours-per-weekEncoded', se crea la gráfica de correlación:

In [194]:
used_features =X.columns[selected]
 
colormap = plt.cm.viridis
plt.figure(figsize=(12,12))
plt.title('Pearson Correlation of Features', y=1.05, size=15)
sb.heatmap(adult_dataframe_encoded[used_features].astype(float).corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor='black', annot=True)
Out[194]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fd60b3aefd0>

P7.3. Split de train y test:

Se obtienen las porciones de datos dedicadas a train y test, siendo iguales a las elegidas para el árbol de decisión (80% train, 20% test).

In [195]:
# Split dataset in training and test datasets
X_train, X_test = train_test_split(adult_dataframe_encoded, test_size=0.2, random_state=6) 
y_train =X_train["incomeEncoded"]
y_test = X_test["incomeEncoded"]

P7.4. Creación del modelo bayesiano:

In [197]:
# Instantiate the classifier
gnb = GaussianNB()
# Train classifier
gnb.fit(
    X_train[used_features].values,
    y_train
)
y_pred = gnb.predict(X_test[used_features])

P7.5. Precisión del modelo de clasificación bayesiana:

In [198]:
print('Accuracy in training set: {:.2f}'
     .format(gnb.score(X_train[used_features], y_train)))
print('Accuracy in test set: {:.2f}'
     .format(gnb.score(X_test[used_features], y_test)))
Accuracy in training set: 0.78
Accuracy in test set: 0.78

P8. Predicciones.

A continuación, se utilizaron algunos datos tanto en el árbol de decisión como en el modelo de clasificación bayesiana para clasificarlos:

P8.1. Predicción Bill Gates:

Se tomaron datos de Bill Gates y fue clasificado correctamente por ambos algoritmos, indicando que tiene ingresos superiores a 50K.

In [199]:
# Bill Gates arbol
x_test = pd.DataFrame(columns=('education-num',	'workclassEncoded',	'marital-statusEncoded',	'occupationEncoded',	'relationshipEncoded',	'raceEncoded',	'sexEncoded',	'native-countryEncoded',	'incomeEncoded',	'ageEncoded',	'capital-gainEncoded',	'capital-lossEncoded',	'hours-per-weekEncoded',	'fnlwgtEncoded'))
x_test.loc[0] = (10,1,0,2,1,0,0,1,1,6,3,0,4,0)
y_pred = decision_tree.predict(x_test.drop(['incomeEncoded'], axis = 1))
print("Prediccion del DT: " + str(y_pred))
y_proba = decision_tree.predict_proba(x_test.drop(['incomeEncoded'], axis = 1))
print("Probabilidad de Acierto: " + str(np.round(y_proba[0][y_pred]* 100, 2)) + "%")
Prediccion del DT: [1]
Probabilidad de Acierto: [67.63]%
In [200]:
#Bill Gates bayesiano

# ['education-num', 'marital-statusEncoded', 'ageEncoded', 'capital-gainEncoded', 'hours-per-weekEncoded']  <-- para bayesiana
billBayesiano = [10, 0, 6, 3, 4]
print("Prediccion Bayesiana: "+ str(gnb.predict([billBayesiano])))
Prediccion Bayesiana: [1]

P8.2. Predicción Obama:

Se tomaron datos de Barack Obama y fue clasificado correctamente por ambos algoritmos, indicando que tiene ingresos superiores a 50K.

In [201]:
# Obama arbol
x_test = pd.DataFrame(columns=('education-num',	'workclassEncoded',	'marital-statusEncoded',	'occupationEncoded',	'relationshipEncoded',	'raceEncoded',	'sexEncoded',	'native-countryEncoded',	'incomeEncoded',	'ageEncoded',	'capital-gainEncoded',	'capital-lossEncoded',	'hours-per-weekEncoded',	'fnlwgtEncoded'))
x_test.loc[0] = (16,0,0,2,1,1,0,1,1,5,3,0,5,0)
y_pred = decision_tree.predict(x_test.drop(['incomeEncoded'], axis = 1))
print("Prediccion del DT: " + str(y_pred))
y_proba = decision_tree.predict_proba(x_test.drop(['incomeEncoded'], axis = 1))
print("Probabilidad de Acierto: " + str(np.round(y_proba[0][y_pred]* 100, 2)) + "%")
Prediccion del DT: [1]
Probabilidad de Acierto: [100.]%
In [202]:
#Obama bayesiano

# ['education-num', 'marital-statusEncoded', 'ageEncoded', 'capital-gainEncoded', 'hours-per-weekEncoded']  <-- para bayesiana
obamaBayesiano = [16, 0, 5, 3, 5]
print("Prediccion Bayesiana: "+ str(gnb.predict([obamaBayesiano])))
Prediccion Bayesiana: [1]

P8.3. Predicción Persona:

Se utilizaron datos de una persona del común como referencia para predecir sus ingresos.

Datos generales:

Mujer, casada, edad >50, diseñadora de modas, independiente, sin ganancias de capital al año, sin pérdidas de capital al año, jornada laboral normal, colombiana, persona de raza blanca y que gana <50K.

In [203]:
# Persona arbol
x_test = pd.DataFrame(columns=('education-num',	'workclassEncoded',	'marital-statusEncoded',	'occupationEncoded',	'relationshipEncoded',	'raceEncoded',	'sexEncoded',	'native-countryEncoded',	'incomeEncoded',	'ageEncoded',	'capital-gainEncoded',	'capital-lossEncoded',	'hours-per-weekEncoded',	'fnlwgtEncoded'))
x_test.loc[0] = (10,3,0,13,2,0,1,6,0,5,0,0,4,1)
y_pred = decision_tree.predict(x_test.drop(['incomeEncoded'], axis = 1))
print("Prediccion del DT: " + str(y_pred))
y_proba = decision_tree.predict_proba(x_test.drop(['incomeEncoded'], axis = 1))
print("Probabilidad de Acierto: " + str(np.round(y_proba[0][y_pred]* 100, 2)) + "%")
Prediccion del DT: [1]
Probabilidad de Acierto: [67.63]%
In [204]:
# Persona bayesiano

# ['education-num', 'marital-statusEncoded', 'ageEncoded', 'capital-gainEncoded', 'hours-per-weekEncoded']  <-- para bayesiana
personaBayesiano = [10, 0, 5, 0, 4]
print("Prediccion Bayesiana: "+ str(gnb.predict([personaBayesiano])))
Prediccion Bayesiana: [0]

CONCLUSIONES:

El árbol de decisión obtuvo una buena precisión para la clasificación de datos. Sin embargo, esta precisión puede mejorar con una mejor categorización de los datos, mayor cantidad de datos y con una mejor evaluación de los parámetros que se ingresan para la creación del árbol.

La clasificación bayesiana fue mejor que el árbol puesto que los 3 ejemplos utilizados para clasificar, los predijo correctamente, mientras que el árbol solo predijo 2 de estos. Puede ser debido a que en la clasificación bayesiana se utilizaron menos atributos en comparación al árbol en el cual se utilizaron todos.