INICIO

Árboles de regresión y random forest para regresión y clasificación
Publicado: <2022-03-18 Fri>

Librerías a utilizar

Para el tratamiento se han agregado principalmente cinco bibliotecas. En el caso de sklearn, se tuvo la necesidad de hacer importaciones parciales para que ciertas funciones y métodos sean detectados correctamente.

from sklearn import metrics
from sklearn import tree
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
from sklearn.model_selection import train_test_split, KFold, cross_val_score
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor, export_graphviz
import matplotlib.pyplot as plt
import numpy as np
import os
import pandas as pd
import seaborn as sns
import sklearn as sl

Carga de datasets

Se usarán dos datasets~\citep{housing_2018} los cuales serán cargados en dos dataframes. Aquel identificado como test solo será usado al final para completar el dataset con el modelo.

df_train = pd.read_csv("./ds/housing_train.csv")
df_test = pd.read_csv("./ds/housing_test.csv")

Resumen de datos

Las dimensiones del dataframe, filas y columnas, se obtiene con la propiedad shape, los valores de las cabeceras se obtienen con la propiedad columns.values.

df_train.shape
(1460, 81)

La función describe() devuelve el conteo de campos no nulos, media, desviación estándar y cuantiles para columnas númericas. En las columnas identificadas como objetos (categóricas) devolverá el conteo de campos no nulos, número de valores posibles, el valor más repetido y su frecuencia. Si se desea saber el tipo de datos que tienen las columnas se usa la propiedad dtypes.

df_train.describe().transpose()
                count           mean           std      min        25%  \
Id             1460.0     730.500000    421.610009      1.0     365.75   
MSSubClass     1460.0      56.897260     42.300571     20.0      20.00   
LotFrontage    1201.0      70.049958     24.284752     21.0      59.00   
LotArea        1460.0   10516.828082   9981.264932   1300.0    7553.50   
OverallQual    1460.0       6.099315      1.382997      1.0       5.00   
OverallCond    1460.0       5.575342      1.112799      1.0       5.00   
YearBuilt      1460.0    1971.267808     30.202904   1872.0    1954.00   
YearRemodAdd   1460.0    1984.865753     20.645407   1950.0    1967.00   
MasVnrArea     1452.0     103.685262    181.066207      0.0       0.00   
BsmtFinSF1     1460.0     443.639726    456.098091      0.0       0.00   
BsmtFinSF2     1460.0      46.549315    161.319273      0.0       0.00   
BsmtUnfSF      1460.0     567.240411    441.866955      0.0     223.00   
TotalBsmtSF    1460.0    1057.429452    438.705324      0.0     795.75   
1stFlrSF       1460.0    1162.626712    386.587738    334.0     882.00   
2ndFlrSF       1460.0     346.992466    436.528436      0.0       0.00   
LowQualFinSF   1460.0       5.844521     48.623081      0.0       0.00   
GrLivArea      1460.0    1515.463699    525.480383    334.0    1129.50   
BsmtFullBath   1460.0       0.425342      0.518911      0.0       0.00   
BsmtHalfBath   1460.0       0.057534      0.238753      0.0       0.00   
FullBath       1460.0       1.565068      0.550916      0.0       1.00   
HalfBath       1460.0       0.382877      0.502885      0.0       0.00   
BedroomAbvGr   1460.0       2.866438      0.815778      0.0       2.00   
KitchenAbvGr   1460.0       1.046575      0.220338      0.0       1.00   
TotRmsAbvGrd   1460.0       6.517808      1.625393      2.0       5.00   
Fireplaces     1460.0       0.613014      0.644666      0.0       0.00   
GarageYrBlt    1379.0    1978.506164     24.689725   1900.0    1961.00   
GarageCars     1460.0       1.767123      0.747315      0.0       1.00   
GarageArea     1460.0     472.980137    213.804841      0.0     334.50   
WoodDeckSF     1460.0      94.244521    125.338794      0.0       0.00   
OpenPorchSF    1460.0      46.660274     66.256028      0.0       0.00   
EnclosedPorch  1460.0      21.954110     61.119149      0.0       0.00   
3SsnPorch      1460.0       3.409589     29.317331      0.0       0.00   
ScreenPorch    1460.0      15.060959     55.757415      0.0       0.00   
PoolArea       1460.0       2.758904     40.177307      0.0       0.00   
MiscVal        1460.0      43.489041    496.123024      0.0       0.00   
MoSold         1460.0       6.321918      2.703626      1.0       5.00   
YrSold         1460.0    2007.815753      1.328095   2006.0    2007.00   
SalePrice      1460.0  180921.195890  79442.502883  34900.0  129975.00   

                    50%        75%       max  
Id                730.5    1095.25    1460.0  
MSSubClass         50.0      70.00     190.0  
LotFrontage        69.0      80.00     313.0  
LotArea          9478.5   11601.50  215245.0  
OverallQual         6.0       7.00      10.0  
OverallCond         5.0       6.00       9.0  
YearBuilt        1973.0    2000.00    2010.0  
YearRemodAdd     1994.0    2004.00    2010.0  
MasVnrArea          0.0     166.00    1600.0  
BsmtFinSF1        383.5     712.25    5644.0  
BsmtFinSF2          0.0       0.00    1474.0  
BsmtUnfSF         477.5     808.00    2336.0  
TotalBsmtSF       991.5    1298.25    6110.0  
1stFlrSF         1087.0    1391.25    4692.0  
2ndFlrSF            0.0     728.00    2065.0  
LowQualFinSF        0.0       0.00     572.0  
GrLivArea        1464.0    1776.75    5642.0  
BsmtFullBath        0.0       1.00       3.0  
BsmtHalfBath        0.0       0.00       2.0  
FullBath            2.0       2.00       3.0  
HalfBath            0.0       1.00       2.0  
BedroomAbvGr        3.0       3.00       8.0  
KitchenAbvGr        1.0       1.00       3.0  
TotRmsAbvGrd        6.0       7.00      14.0  
Fireplaces          1.0       1.00       3.0  
GarageYrBlt      1980.0    2002.00    2010.0  
GarageCars          2.0       2.00       4.0  
GarageArea        480.0     576.00    1418.0  
WoodDeckSF          0.0     168.00     857.0  
OpenPorchSF        25.0      68.00     547.0  
EnclosedPorch       0.0       0.00     552.0  
3SsnPorch           0.0       0.00     508.0  
ScreenPorch         0.0       0.00     480.0  
PoolArea            0.0       0.00     738.0  
MiscVal             0.0       0.00   15500.0  
MoSold              6.0       8.00      12.0  
YrSold           2008.0    2009.00    2010.0  
SalePrice      163000.0  214000.00  755000.0  
df_train.describe(include='object').transpose()
              count unique      top  freq
MSZoning       1460      5       RL  1151
Street         1460      2     Pave  1454
Alley            91      2     Grvl    50
LotShape       1460      4      Reg   925
LandContour    1460      4      Lvl  1311
Utilities      1460      2   AllPub  1459
LotConfig      1460      5   Inside  1052
LandSlope      1460      3      Gtl  1382
Neighborhood   1460     25    NAmes   225
Condition1     1460      9     Norm  1260
Condition2     1460      8     Norm  1445
BldgType       1460      5     1Fam  1220
HouseStyle     1460      8   1Story   726
RoofStyle      1460      6    Gable  1141
RoofMatl       1460      8  CompShg  1434
Exterior1st    1460     15  VinylSd   515
Exterior2nd    1460     16  VinylSd   504
MasVnrType     1452      4     None   864
ExterQual      1460      4       TA   906
ExterCond      1460      5       TA  1282
Foundation     1460      6    PConc   647
BsmtQual       1423      4       TA   649
BsmtCond       1423      4       TA  1311
BsmtExposure   1422      4       No   953
BsmtFinType1   1423      6      Unf   430
BsmtFinType2   1422      6      Unf  1256
Heating        1460      6     GasA  1428
HeatingQC      1460      5       Ex   741
CentralAir     1460      2        Y  1365
Electrical     1459      5    SBrkr  1334
KitchenQual    1460      4       TA   735
Functional     1460      7      Typ  1360
FireplaceQu     770      5       Gd   380
GarageType     1379      6   Attchd   870
GarageFinish   1379      3      Unf   605
GarageQual     1379      5       TA  1311
GarageCond     1379      5       TA  1326
PavedDrive     1460      3        Y  1340
PoolQC            7      3       Gd     3
Fence           281      4    MnPrv   157
MiscFeature      54      4     Shed    49
SaleType       1460      9       WD  1267
SaleCondition  1460      6   Normal  1198
df_train.dtypes
Id                 int64
MSSubClass         int64
MSZoning          object
LotFrontage      float64
LotArea            int64
                  ...   
MoSold             int64
YrSold             int64
SaleType          object
SaleCondition     object
SalePrice          int64
Length: 81, dtype: object

Para obtener detalles por columna podemos usar df_train['Nombre de columna'].describe(), también es posible obtener por columna los posibles valores posibles y sus respectivas frecuencias, como en el siguiente ejemplo.

df_train["SaleType"].value_counts()
WD       1267
New       122
COD        43
ConLD       9
ConLI       5
ConLw       5
CWD         4
Oth         3
Con         2
Name: SaleType, dtype: int64

Matriz de correlación

La matriz de correlación indicará que tan fuerte o débil es la relación entre dos variables. Puede leerse por columnas o por filas. En la siguiente imagen se eliminó la columna Id, porque no será relevante para el análisis. La claridad de la celda es directamente proporcional a una mayor correlación.

df_train = df_train.drop(columns = ['Id'])
plt.figure(figsize=(20,8),dpi=80)
corrmat = df_train.corr()
sns.heatmap(corrmat, vmax=.8, fmt='.1f', annot=True)
<AxesSubplot:>

output_13_1.png

De esta forma podemos saber que variables están más relacionadas con otras. en el caso de la variable SalePrice las diez variables más útiles serán aquellas con mayor índice de correlación, mismas que se usarán posteriormente para las predicciones.

df_train.corr()['SalePrice'].sort_values(ascending=False)[1:11]
OverallQual     0.790982
GrLivArea       0.708624
GarageCars      0.640409
GarageArea      0.623431
TotalBsmtSF     0.613581
1stFlrSF        0.605852
FullBath        0.560664
TotRmsAbvGrd    0.533723
YearBuilt       0.522897
YearRemodAdd    0.507101
Name: SalePrice, dtype: float64

Valores perdidos

Trabajar con los valores perdidos requiere primero su ubicación, posteriormente se seleccionará que debe ser borrado y luego que debe ser sustituido con un nuevo valor, por supuesto habrá que decidir cual será dicho valor nuevo.

Eliminar campos

Para ubicar si una celda tiene un valor vacío se usa la función isnull(), si se prefiere lógica inversa se usa notnull. Es posible obtener un vector de estos valores con la propiedad values, transformarlo a un array con la función ravel() y sumar los valores verdaderos con la función sum(). También es posible obtener una lista ordenada de las columnas con más valores vacíos.

df_train.isnull().sum().sort_values(ascending=False)[0:19]
PoolQC          1453
MiscFeature     1406
Alley           1369
Fence           1179
FireplaceQu      690
LotFrontage      259
GarageYrBlt       81
GarageCond        81
GarageType        81
GarageFinish      81
GarageQual        81
BsmtExposure      38
BsmtFinType2      38
BsmtCond          37
BsmtQual          37
BsmtFinType1      37
MasVnrArea         8
MasVnrType         8
Electrical         1
dtype: int64

En el ejemplo de arriba, el valor es el número de valores vacíos, si usamos la función notnull() sería el número de valores no vacíos, la suma de ambos debe ser el número total de filas obtenido anteriormente.

Hay dos razones para la falta de valores en los datasets:

  • Recolección de datos: No se consiguieron los datos.
  • Extracción de datos: Los datos están en la DB original pero no se extrajeron correctamente al dataset.

Se deben evitar datos vacíos para no tener problemas de manejo de información. Se tienen dos opciones:

  • Borrar las filas donde falten valores en alguna de las columnas
  • Borrar las columnas donde no se tenga suficiente información

En este ejercicio es posible observar que las columnas MiscFeature, Fence, PoolQC, FirePlaceQu y Alley tienen muy pocos valores proporcionados (menos del 55 por ciento)y no vale la pena conservarlas. Otro criterio para asegurar un buen curso de acción es revisar las correlaciones con la columna SalePrice.

Como el razonamiento es el correcto se procede al borrado de columnas.

def toDel(df):
    for col in df.columns.values:
        nv = pd.isnull(df[col]).values.ravel().sum()
        if nv > df.shape[0] * 0.45:
            print("Deleting: "+col)
            del df[col]
    return df
df_train = toDel(df_train)
Deleting: Alley
Deleting: FireplaceQu
Deleting: PoolQC
Deleting: Fence
Deleting: MiscFeature

Llenar campos

Es necesario detectar nuevamente que columnas tienen valores vacíos. Esta vez se reemplazarán esos valores. Hay valores númericos y categóricos vacíos; los numéricos serán reemplazados por el promedio original de la columna, los categóricos serán remplazados por el valor no nulo más cercano puede ser el valor que va antes (ffill) o el que va después (bfill), en este análisis será el segundo.

Es necesario señalar que el procedimiento más preciso para las columnas categóricas sería colocar el valor de mayor frecuencia relacionado con el valor de la columna objetivo, por ejemplo: Si la columna Y del dataframe es la variable dependiente y X es una columna categórica con valores perdidos; dichos valores se llenarán por aquel de mayor frecuencia en X tomando en cuenta solo aquellos con los que coincidan en Y. Más adelante se retomará la justificación de porque no se ha hecho de esta forma.

def DetectNull(df):
    candidates = []
    for col in df.columns.values:
        nv = pd.isnull(df[col]).values.ravel().sum()
        if nv > 0:
            candidates.append((col, df[col].dtype, nv))
    return candidates
def FillNull(df, list):
    for col in list:
        if col[1] == 'float64':
            df[col[0]] = df[col[0]].fillna(df[col[0]].mean())
        else:
            df[col[0]] = df[col[0]].fillna(method="bfill")
    return df
df_test = FillNull(df_test, DetectNull(df_test))
df_train = FillNull(df_train, DetectNull(df_train))
df_train.isnull().sum().sort_values(ascending=False)
MSSubClass      0
GarageYrBlt     0
Fireplaces      0
Functional      0
TotRmsAbvGrd    0
               ..
MasVnrArea      0
MasVnrType      0
Exterior2nd     0
Exterior1st     0
SalePrice       0
Length: 75, dtype: int64

Problema de regresión

Árboles de decisión

Primero serán creados los conjuntos de prueba y entrenamiento. Serán usados para el modelo solo aquellos campos que tengan un alto índice de correlación en la matriz de correlaciones mostrada anteriormente.

train, test = train_test_split(df_train, test_size=0.2)
predictors = ['OverallQual', 'GrLivArea', 'GarageCars', 'GarageArea', 'TotalBsmtSF', '1stFlrSF', 'FullBath', 'TotRmsAbvGrd', 'YearBuilt', 'YearRemodAdd']
target = ['SalePrice']

A continuación se entrena el árbol de regresión y se ingresan los datos para probar la predicción del mismo.

dtr = DecisionTreeRegressor(max_depth=15, min_samples_split=20, random_state=99)
dtr.fit(train[predictors], train[target])
prediction = dtr.predict(test[predictors])

Ahora se muestran los resultados: una comparación entre los datos originales y las predicciones, además, el árbol obtenido, mismo que fue guardado en la carpeta out del proyecto en formato graphviz y como imagen.~\citep{Galarnyk_2021}

test['preds'] = prediction
test[['SalePrice','preds']]
      SalePrice          preds
520      106250   62509.090909
562      108000  130822.222222
265      175500  176128.571429
1228     367294  337085.071429
280      228500  199144.444444
...         ...            ...
670      173500  176352.631579
102      118964  136842.947368
1130     135000  145525.777778
1016     203000  263613.333333
12       144000  119778.571429

[292 rows x 2 columns]

En la tabla se observa que muchos valores de predicción están repetidos, esto se debe a que entran en la misma lógica de predicción. Debe recordarse que el árbol funciona decidiendo con valores preestablecidos.

with open('out/dtr.dot','w') as dotfile:
    export_graphviz(dtr, out_file=dotfile, feature_names=predictors)
    dotfile.close()
tree.plot_tree(dtr);

output_33_0.png

Para validar el modelo se usará un método de validación cruzada, un método estadístico para evaluar y comparar algoritmos de aprendizaje dividiendo datos en dos segmentos: entrenamiento y prueba. Típicamente, ambos conjuntos deben cruzarse en rondas sucesivas de modo que cada punto de datos tenga la posibilidad de ser validado. La forma básica es la validación cruzada k-fold.~\citep{Refaeilzadeh_Tang_Liu_2018}

dtr = DecisionTreeRegressor(max_depth=15, min_samples_split=20, random_state=99)
dtr.fit(train[predictors], train[target])
cv = KFold(n_splits = 20, shuffle = True, random_state = 1)
score = np.mean(cross_val_score(dtr, train[predictors], train[target], scoring = "neg_mean_squared_error", cv = cv, n_jobs = 1))
score
-1695546565.4410434

El modelo es muy deficiente según el error cuadrático medio de pérdida. Este error es muy grande, debería ser cercano a cero. Sería mejor probar un modelo lineal, los árboles de regresión son útiles si es necesario estimar un modelo no lineal. Para confirmar se realizará este modelo bajo diferentes profundidades del árbol, de esta forma se podría encontrar un mejor conjunto de parámetros, no sucede en este caso.

for i in range(1,21):
    dtr = DecisionTreeRegressor(max_depth=i, min_samples_split=20, min_samples_leaf=5,random_state=99)
    dtr.fit(train[predictors], train[target])
    cv = KFold(n_splits = 20, shuffle = True, random_state = 1)
    score = np.mean(cross_val_score(dtr, train[predictors], train[target], scoring = "neg_mean_squared_error", cv = cv, n_jobs = 1))
    print("Score para i=",i,": ",score)
Score para i= 1 :  -3518061167.0543084
Score para i= 2 :  -2579254167.548243
Score para i= 3 :  -2153519070.3027043
Score para i= 4 :  -1785517029.0882308
Score para i= 5 :  -1658794897.3649328
Score para i= 6 :  -1611813883.5452306
Score para i= 7 :  -1551394173.9604716
Score para i= 8 :  -1554754004.1369624
Score para i= 9 :  -1547076489.8592367
Score para i= 10 :  -1551932650.1670842
Score para i= 11 :  -1551095985.7058103
Score para i= 12 :  -1551446872.7832217
Score para i= 13 :  -1551400950.2026584
Score para i= 14 :  -1549894543.9422781
Score para i= 15 :  -1550710493.3735516
Score para i= 16 :  -1552723780.9771752
Score para i= 17 :  -1552723780.9771752
Score para i= 18 :  -1552723780.9771752
Score para i= 19 :  -1552723780.9771752
Score para i= 20 :  -1552723780.9771752

Random Forest

Al igual que en la sección anterior, se entrena el modelo con los mismos conjuntos definidos anteriormente y se hace una predicción.

rfr = RandomForestRegressor(n_jobs = 1, oob_score=True, n_estimators=10000)
rfr.fit(train[predictors], train[target].values.ravel())
prediction = rfr.predict(test[predictors])
test['preds'] = prediction
test[['SalePrice','preds']]
      SalePrice          preds
520      106250   96389.837800
562      108000  107334.079900
265      175500  178429.007700
1228     367294  332232.461100
280      228500  211677.532000
...         ...            ...
670      173500  176562.496908
102      118964  129663.313500
1130     135000  140604.314200
1016     203000  230900.547700
12       144000  113294.444000

[292 rows x 2 columns]

Como puede verse, un bosque de diez mil árboles las estimaciones los valores se acercan notablemente. Esto puede confirmarse con la puntuación propia del bosque, la cual funciona como el coeficiente de determinación de un modelo de regresión.

rfr.oob_score_
0.8086789376427154

La conclusión es que sería mejor usar un modelo de regresión que un modelo de decisión porque pese a la mejora sustancial respecto al árbol anterior, el bosque no alcanza un 0.9 en la puntuación, condición que se le exigiría a un modelo lineal. Debido a que este es el mejor modelo obtenido, lo usaremos para df_test.

df_test['SalePrice'] = rfr.predict(df_test[predictors])
df_test.to_csv('out/housing_test_complete.csv')

Problema de clasificación

Creación de categorías de SalesPrice

Ahora se procederá a crear categorías con la columna SalePrice. Para ello se ha escrito una función y una nueva columna dentro del dataframe.

def SalePriceGroupValue(x):
    if x >= 500001:
        return 'G3'
    elif x <= 100000:
        return 'G1'
    return 'G2'
df_train["SalePriceGroup"] = df_train["SalePrice"].apply(SalePriceGroupValue)
df_train["SalePriceGroup"].value_counts()
G2    1328
G1     123
G3       9
Name: SalePriceGroup, dtype: int64

Aquí es posible observar que la gran mayoria de los datos se encuentran en la categoría G2. Esto confirma que la opción antes seleccionada para llenar datos perdidos es buena debido a que binda una posibilidad de preservar datos las otras categorías.

Árboles de decisión

Se repetirá el procedimiento visto anteriormente, la diferencia es que ahora usará DecisionTreeClassifier.~\citep{Navlani_2018}

train, test = train_test_split(df_train, test_size=0.2)
colnames = df_train.columns.values.tolist()
predictors = ['OverallQual', 'GrLivArea', 'GarageCars', 'GarageArea', 'TotalBsmtSF', '1stFlrSF', 'FullBath', 'TotRmsAbvGrd', 'YearBuilt', 'YearRemodAdd']
target = colnames[75]
dtc = DecisionTreeClassifier(criterion="entropy", max_depth=3, min_samples_split=20, random_state=99)
dtc.fit(train[predictors], train[target])
prediction = dtc.predict(test[predictors])

Se ha creado una tabla cruzada que logra visualizar los resultados, además es posible usar metricas simples para verificar la exactitud del árbol.

pd.crosstab(test[target], prediction, colnames=["Predictions"], rownames=["Real"])
Predictions  G1   G2
Real                
G1           10   11
G2           12  257
G3            0    2
print("Accuracy: ", metrics.accuracy_score(prediction, test[target]))
Accuracy:  0.9143835616438356
with open('out/dtc.dot','w') as dotfile:
    export_graphviz(dtc, out_file=dotfile, feature_names=predictors)
    dotfile.close()
tree.plot_tree(dtc);

output_55_0.png

Al usar nuevamente validación cruzada se observa que una buena clasificación esta entre i=3 e i=6, lo que significa que si se deja crecer el árbol desde el nodo raíz con estas profundidades es posible obtener clasificaciones óptimas. También podemos ver que las variables de mayor importancia clasificatoria son TotalBsmtSF y GrLivArea.

for i in range(1,10):
    dtc = DecisionTreeClassifier(criterion="entropy", max_depth=i, min_samples_split=20, random_state=99)
    dtc.fit(train[predictors], train[target])
    cv = KFold(n_splits = 20, shuffle = True, random_state = 1)
    score = np.mean(cross_val_score(dtc, train[predictors], train[target], scoring = "accuracy", cv = cv, n_jobs = 1))
    print("Score para i=",i,": ",score)
    print("Importancia de variables: \n\t",dtc.feature_importances_)
Score para i= 1 :  0.9066481589713618
Importancia de variables: 
       [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
Score para i= 2 :  0.9323641145528929
Importancia de variables: 
       [0.         0.46494326 0.         0.         0.53505674 0.
 0.         0.         0.         0.        ]
Score para i= 3 :  0.9332261835184102
Importancia de variables: 
       [0.         0.33238498 0.         0.09651566 0.46853394 0.
 0.         0.         0.         0.10256542]
Score para i= 4 :  0.9366306253652835
Importancia de variables: 
       [0.         0.31621815 0.         0.07775052 0.41561868 0.02302104
 0.         0.         0.06193988 0.10545173]
Score para i= 5 :  0.9391729982466395
Importancia de variables: 
       [0.02617778 0.31151851 0.         0.08319446 0.38373239 0.02125486
 0.         0.         0.03762721 0.13649479]
Score para i= 6 :  0.939187609585038
Importancia de variables: 
       [0.02517877 0.31133357 0.         0.08882965 0.37821928 0.02044373
 0.         0.         0.03619127 0.13980373]
Score para i= 7 :  0.9383255406195208
Importancia de variables: 
       [0.03296921 0.30531763 0.01780554 0.07541623 0.35646125 0.01926765
 0.         0.         0.06100131 0.13176117]
Score para i= 8 :  0.9340590298071303
Importancia de variables: 
       [0.03224009 0.28731886 0.01741177 0.07374839 0.35982465 0.02625347
 0.         0.         0.06609772 0.13710506]
Score para i= 9 :  0.9331969608416131
Importancia de variables: 
       [0.03174187 0.29167446 0.0171427  0.07260873 0.35426417 0.02584777
 0.         0.         0.07173396 0.13498634]

Random Forest

En la implementación de este bosque se usa el mismo procedimiento visto anteriormente, es importante poner atención en los cambios de los argumentos de cada árbol, cada implementación dependerá del problema.

rfc = RandomForestClassifier(n_jobs = 1, oob_score=True, n_estimators=10000)
rfc.fit(train[predictors], train[target])
prediction = rfc.predict(test[predictors])
test['preds'] = prediction
test[['SalePriceGroup','preds']]
     SalePriceGroup preds
1378             G1    G1
218              G2    G2
1328             G2    G2
194              G2    G2
919              G2    G2
...             ...   ...
415              G2    G2
1227             G2    G2
928              G2    G2
1354             G2    G2
362              G2    G2

[292 rows x 2 columns]

En esta ocasión se ha aumentado la exactitud del árbol, es posible decir que se ha creado un modelo confible. Debido a que este es el mejor modelo obtenido, lo usaremos para df_test.

rfc.oob_score_
0.9417808219178082
df_test['SalePriceGroup'] = rfc.predict(df_test[predictors])
df_test.to_csv('out/housing_test_complete.csv')

Conclusión

En esta actividad se retomó el USA Housing Dataset, el cual presenta datos sobre la venta de casas en Estados Unidos. Se describieron los datos y se creó una matriz de corelaciones para obtener aquellos más importantes para el análisis. Como segundo paso se realizó una limpieza de datos, se eliminaron columnas innecesarias y llenaron valores perdidos bajo criterios claros. Una vez realizadas dichas operaciones fue posible entrenar árboles de desición y random forest.

El dataset no tiene una descripción clara de los datos proporcionados, aunque es posible analizarlos e inferir algunos de los significados, una de los requisitos más importantes para el análisis siempre será una descripción inicial clara de los mismos, incluso la interpretación de los resultados depende de ello.

Para entrenar los modelos se usaron variables númericas, con esto se obtuvieron resultados positivos en lo general. Si se hubieran usado variables categóricas habría la necesidad de procesarlas y crear varibles separadas (variables dummy, con la función pd.get_dummies). Es importante decir que el árbol de regresión obtuvo un mal modelo, sin embargo, el bosque de regresión presentó resultados mucho mejores, aunque sería interesante compararlo con un modelo lineal en un trabajo futuro. Si el problema se convierte en categórico los resultados mejoran notablemente, la principal razón es que las hojas de los árboles siempre corresponden a una categoría, incluso en regresión, por lo tanto, el árbol de regresión será una opción si otros modelos de regresión no tuvieron resultados satisfactorios.

Otro punto a mencionar es que los modelos probados son ajustables mediante muchos parámetros, su ajuste dependerá del problema que se enfrenta y hasta cierto punto es un proceso de ajuste a prueba y error. Además el proceso de validación cruzada es de gran ayuda para evitar el crecimiento excesivo de los árboles.

Finalmente, decir que los procedimientos aquí enunciados, junto con el código en forma de notebook de Python y los resultados en formato PDF son visibles en los archivos de código disponibles en el repositorio del proyecto.

Autor: Edgar Uriel Domínguez Espinoza (edgar_uriel84 AT genomorro DOT name)

Última modificación: 2023-08-11 Fri 01:36

Emacs 29.1 (Org mode 9.6.6)