Caso de Estudio: Predicción de Retornos Anuales en Fondos de Renta Variable

FZ2022 Algoritmos y Análisis de Datos

Author
Affiliation

Sergio Castellanos-Gamboa, PhD

Tecnológico de Monterrey

Published

October 24, 2024

1 Escenario:

Eres un gestor de fondos de renta variable con el objetivo de identificar qué empresas mejorarán su margen operativo (EBIT / Ventas) en el siguiente trimestre. Para ello, dispones de datos trimestrales financieros de varias empresas y deberás transformarlos en crecimientos porcentuales trimestrales para construir un modelo predictivo que ayude a anticipar el desempeño futuro de estas empresas.


2 Objetivo del Caso:

Desarrollar un modelo de Machine Learning para predecir si el margen operativo de una empresa crecerá porcentualmente o no, en términos trimestrales. Para esto, se requiere:

  1. A partir de los datos trimestrales, calcular el margen operativo para cada empresa.
  2. Crear la variable dependiente margen_operativo_p, que será igual a 1 si el crecimiento trimestral del margen operativo de la empresa es positivo y 0 en caso contrario.
  3. Calcular las variables independientes del modelo predictivo a partir de razones financieras.
  4. Construir un modelo de predicción y realizar una partición de datos en un conjunto de entrenamiento y prueba.
  5. Documentar detalladamente cada uno de los pasos e interpretar las salidas del modelo y sus resultados.

3 Instrucciones y Lineamientos

  1. Cálculo del Margen Operativo
    • La base de datos contiene ventas y costos trimestrales, a partir de los cuales puedes calcular el margen operativo. El margen operativo se define como: Margen\_Operativo = \frac{EBIT}{revenue} donde: EBIT = \text{revenue} - \text{cogs} - \text{sgae}
      • El EBIT (Earnings Before Interest and Taxes) representa las ganancias antes de intereses e impuestos, calculado restando los costos operativos (cogs y sgae).
    • Una vez calculado el margen operativo, es necesario estimar el cambio porcentual trimestral del margen operativo para capturar el crecimiento o disminución en comparación con el trimestre anterior. El cambio porcentual se calcula comparando el margen operativo actual con el margen del trimestre anterior: Crecimiento\_Margen\_Operativo = \left( \frac{Margen\_Operativo_{t}}{Margen\_Operativo_{t-1}} - 1 \right) donde:
      • Margen_Operativo_{t} es el margen operativo del trimestre actual.
      • Margen_Operativo_{t-1} es el margen operativo del trimestre anterior.
      Este cálculo nos da el cambio porcentual, lo que indica si el margen operativo ha crecido o decrecido en términos trimestrales.
  2. Creación de la Variable Dependiente: Margen Operativo Positivo
    • Con el crecimiento porcentual del margen operativo calculado, se puede crear la variable dependiente margen_operativo_p, que será una variable binaria. Esta variable toma el valor de:
      • 1 si el crecimiento del margen operativo es positivo, es decir, el margen operativo ha aumentado en términos trimestrales.
      • 0 si el crecimiento del margen operativo es negativo o cero.
      Matemáticamente, se define como: margen\_operativo\_p = \begin{cases} 1 & \text{si } Crecimiento\_Margen\_Operativo > 0 \\ 0 & \text{si } Crecimiento\_Margen\_Operativo \leq 0 \end{cases}
  3. Selección de Variables Independientes
    • Crea dos variables independientes que ayuden a predecir si el crecimiento del margen operativo será positivo:
      • Apalancamiento Financiero: \frac{longdebt}{totalassets} Relación entre la deuda a largo plazo y los activos totales de la empresa, lo cual nos indica el grado de apalancamiento.
      • Crecimiento de Activos: El crecimiento trimestral de los activos totales, que mide el incremento o decremento de los activos de la empresa en comparación con el trimestre anterior. Se define como: Crecimiento\_de\_Activos = \left(\frac{totalassets_{t}}{totalassets_{t-1}}-1\right) donde:
      • totalassets_{t} representa los activos totales del trimestre actual.
      • totalassets_{t-1} representa los activos totales del trimestre anterior.
  4. Construcción del Modelo de Machine Learning
    • Usa un modelo de regresión logística con la variable dependiente margen_operativo_p y las dos variables independientes seleccionadas.
    • División de Datos: Realiza una partición del 80/20 para entrenar y probar el modelo.
    • Matriz de Confusión: Utiliza la matriz de confusión para evaluar la precisión del modelo y entender su capacidad predictiva.
  5. Interpretación de Resultados
    • Interpreta los coeficientes del modelo logístico y discute qué variables resultaron ser más importantes o significativas.
    • Reflexiona sobre cómo estas variables te ayudan a predecir si el crecimiento del margen operativo será positivo.
    • Analiza la utilidad del modelo de Machine Learning a partir de la matriz de confusión, sus verdaderos positivos y negativos, así como sus falsos positivos y negativos.

4 Recursos Permitidos

  • Puedes hacer uso de las soluciones de workshops anteriores y de tus notas personales para resolver el caso.

5 Reglas del Juego y Advertencias

  • No está permitido el uso de inteligencia artificial generativa (ChatGPT, Gemini, etc.) para resolver el caso.
  • Evita cometer FIAS (Faltas a la Integridad Académica):
    • Citar o tomar trabajo de otros sin darle crédito correspondiente.
    • Plagio o colaboración no permitida con otros compañeros.
  • Tiempo asignado: 1 hora y 30 minutos.
  • Elabora y documenta tu proceso de forma clara y estructurada, explicando cada decisión que tomes.
    • No incluyas códigos que no sean necesarios. El objetivo no es hacer copy+paste de los workshops pasados.

6 Rúbrica de Evaluación

La rúbrica a continuación detalla los puntos asignados a cada sección clave del caso, con los porcentajes respectivos para cada criterio.

Criterio Sobresaliente (100%) Sólido (90%) Básico (75%) Incipiente (50%) Porcentaje
1. Algoritmos y Manejo de Datos Implementación correcta del manejo de datos con dplyr, cálculo preciso del retorno anual, creación precisa de la variable dependiente y filtrado adecuado por fiscalmonth == 12. Manejo de datos correcto, pero con errores menores en el cálculo del retorno o en la creación de la variable dependiente. Cálculo incorrecto del retorno anual o de la variable dependiente, con errores evidentes en el manejo de los datos o el filtrado. No se realiza ni el cálculo del retorno anual, ni la creación de la variable dependiente, ni el filtrado de los datos. 30%
2. Cálculo de Razones Financieras Cálculo y selección clara de al menos tres razones financieras relevantes y bien justificadas. Cálculo correcto de razones financieras, pero con explicaciones superficiales o insuficientes. Selección pobre o cálculo incorrecto de las razones financieras. No se calculan ni seleccionan razones financieras. 20%
3. Modelo de Machine Learning Modelo implementado correctamente con partición 80/20, matriz de confusión y análisis detallado de resultados. Modelo implementado correctamente, pero con una interpretación incompleta o sin análisis profundo de resultados. Implementación incompleta del modelo o errores en la partición de los datos y en la matriz de confusión. No se implementa un modelo de Machine Learning. 30%
4. Interpretación de Resultados Interpretación coherente y detallada de los resultados y su impacto en la predicción de los retornos. Interpretación correcta, pero sin suficiente profundidad o análisis crítico. Interpretación superficial o poco clara de los resultados. No se realiza interpretación de resultados. 20%

¡Muchos éxitos!


7 Solución

7.1 Instalar y cargar las liberarías

# Check if the dplyr package is installed; if not, install it
if(!require(dplyr)) install.packages("dplyr")
# Load the dplyr package
library(dplyr)

# Check if the statar package is installed; if not, install it
if(!require(caret)) install.packages("caret")
# Load the statar package
library(caret)

7.2 Descargar los datos

# Descargar el archivo CSV del sitio web:
download.file("http://www.apradie.com/datos/uspanel1.csv", "uspanel1.csv")

# Importar el panel
uspanel <- read.csv("uspanel1.csv")

Lista de las variables en el dataset

head(uspanel)
  firm      q fiscalmonth revenue   cogs   sgae otherincome extraordinaryitems
1 BCPC 2021q1           3  185656 126929  28152         133                  0
2 BCPC 2021q2           6  388021 269847  57006         167                  0
3 BCPC 2021q3           9  585890 406782  85427         295                  0
4 BCPC 2021q4          12  799023 555849 115672         187                  0
5 BCPC 2022q1           3  228867 157361  33170        -161                  0
6 BCPC 2022q2           6  465560 322178  65126         137                  0
  finexp incometax totalassets currentassets inventory totalliabilities
1    725      6572     1169215        281204     77022           319723
2   1333     13860     1173606        287263     78333           303964
3   1889     20932     1179349        300275     81925           290271
4   2456     29129     1199325        322232     91058           322310
5    545      8700     1194908        319804    108411           321226
6   1505     18176     1606674        377141    140840           705304
  currentliabilities longdebt adjprice originalprice sharesoutstanding year
1              88763   172337 123.1968        125.41          32392.81 2021
2             100759   143660 128.9436        131.26          32435.69 2021
3             104926   129069 142.5098        145.07          32370.53 2021
4             143802   129395 166.2726        168.60          32381.61 2021
5             123529   150664 134.8130        136.70          32187.35 2022
6             144143   458794 127.9491        129.74          32116.46 2022
  fixedassets yearf cto fiscalq       Nombre status partind
1      226513  2021   1       1 Balchem Corp activo      NA
2      228289  2021   2       2 Balchem Corp activo      NA
3      229798  2021   3       3 Balchem Corp activo      NA
4      237517  2021   4       4 Balchem Corp activo      NA
5      240419  2022   1       1 Balchem Corp activo      NA
6      252145  2022   2       2 Balchem Corp activo      NA
                     naics1            naics2 SectorEconomatica
1 Industrias manufactureras Industria química           Química
2 Industrias manufactureras Industria química           Química
3 Industrias manufactureras Industria química           Química
4 Industrias manufactureras Industria química           Química
5 Industrias manufactureras Industria química           Química
6 Industrias manufactureras Industria química           Química

8 Creación de variables

# Calcular las variables dependientes e independientes.  
uspanel <- uspanel %>%
  group_by(firm) %>%
  # Organizar los datos por empresa y trimestre
  arrange(firm, q) %>%
  # Calcular el EBIT, el mar
  mutate(ebit = revenue - cogs - sgae,
         margen_operativo = ifelse(revenue == 0, NA, ebit / revenue),
         crecimiento_margen = margen_operativo / lag(margen_operativo) -1,
         apalancamiento = ifelse(totalassets == 0, NA, longdebt / totalassets),
         crecimiento_activos = ifelse(totalassets == 0, NA, totalassets / lag(totalassets) - 1),
         margen_operativo_p = ifelse(crecimiento_margen > 0, 1, 0))

# Mostrar las primeras observaciones de las variables del modelo
uspanel %>%
  select(firm, q, margen_operativo, margen_operativo_p, apalancamiento, crecimiento_activos) %>%
  head()
# A tibble: 6 × 6
# Groups:   firm [1]
  firm  q      margen_operativo margen_operativo_p apalancamiento
  <chr> <chr>             <dbl>              <dbl>          <dbl>
1 BCPC  2021q1            0.165                 NA          0.147
2 BCPC  2021q2            0.158                  0          0.122
3 BCPC  2021q3            0.160                  1          0.109
4 BCPC  2021q4            0.160                  0          0.108
5 BCPC  2022q1            0.168                  1          0.126
6 BCPC  2022q2            0.168                  1          0.286
# ℹ 1 more variable: crecimiento_activos <dbl>

9 Entrenamiento del modelo

Creamos la partición 80/20

set.seed(123456)

# Aleatorizar el índice de las filaes:
rows_shuffled<-sample(nrow(uspanel))

# Aleatorizar el orden de los datos 
shuffled_uspanel <- uspanel[rows_shuffled, ]

Dejamos las variables que se usarán en el modelo

shuffled_uspanel <- shuffled_uspanel %>% 
              select(firm, q, margen_operativo_p, apalancamiento, crecimiento_activos) 
head(shuffled_uspanel)
# A tibble: 6 × 5
# Groups:   firm [5]
  firm  q      margen_operativo_p apalancamiento crecimiento_activos
  <chr> <chr>               <dbl>          <dbl>               <dbl>
1 HUN   2021q4                  0         0.203              0.0960 
2 OLN   2022q4                  0         0.356             -0.0214 
3 ECVT  2024q2                  1         0.479             -0.00594
4 IOSP  2021q1                 NA         0.0172            NA      
5 FMC   2023q4                  0         0.254              0.0885 
6 OLN   2021q2                  0         0.425              0.0241 
# Dividimos los datos entre la base de entrenamiento y la de evaluación y creamos los dos sets

split <- round(nrow(shuffled_uspanel)*.80)
split
[1] 90
# Crear base de entrenamiento
train <- shuffled_uspanel[1:split, ]

# Crear base de evaluación
test <- shuffled_uspanel[(split+1):nrow(shuffled_uspanel), ]

Correr el modelo de regresión logísitca con la base de entrenamiento

logit_train <- glm(margen_operativo_p ~ apalancamiento + crecimiento_activos, data= train, family="binomial",na.action=na.omit)
summary(logit_train)

Call:
glm(formula = margen_operativo_p ~ apalancamiento + crecimiento_activos, 
    family = "binomial", data = train, na.action = na.omit)

Coefficients:
                    Estimate Std. Error z value Pr(>|z|)
(Intercept)          -0.3815     0.4969  -0.768    0.443
apalancamiento        0.4361     1.3525   0.322    0.747
crecimiento_activos  -3.5313     4.2639  -0.828    0.408

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 111.29  on 80  degrees of freedom
Residual deviance: 110.31  on 78  degrees of freedom
  (9 observations deleted due to missingness)
AIC: 116.31

Number of Fisher Scoring iterations: 4

10 Predicciones y evaluación del modelo

Luego, se crean las predicciones de la probabilidad de ocurrencia del evento con el modelo.

test$margen_operativo_p_predprob = predict(logit_train,newdata=test, type="response")
head(test)
# A tibble: 6 × 6
# Groups:   firm [5]
  firm  q      margen_operativo_p apalancamiento crecimiento_activos
  <chr> <chr>               <dbl>          <dbl>               <dbl>
1 OLN   2022q2                  0         0.331             0.0117  
2 IOSP  2021q2                  1         0.0167            0.0250  
3 FMC   2024q1                  0         0.253             0.00438 
4 BCPC  2023q1                  0         0.292             0.00262 
5 CC    2021q1                 NA         0.580            NA       
6 OLN   2021q3                  1         0.363            -0.000520
# ℹ 1 more variable: margen_operativo_p_predprob <dbl>

Para asignar cuándo el modelo predice la ocurrencia del evento, debo asignar un punto de corte. En este caso, si el modelo predice la ocurrencia con una probabilidad mayor al 50%, entonces determino que el evento ocurrirá.

# Crear margen_operativo_p_pred 
test <- test %>% 
  mutate(margen_operativo_p_pred = ifelse(margen_operativo_p_predprob > 0.5, 1, 0))
head(test)
# A tibble: 6 × 7
# Groups:   firm [5]
  firm  q      margen_operativo_p apalancamiento crecimiento_activos
  <chr> <chr>               <dbl>          <dbl>               <dbl>
1 OLN   2022q2                  0         0.331             0.0117  
2 IOSP  2021q2                  1         0.0167            0.0250  
3 FMC   2024q1                  0         0.253             0.00438 
4 BCPC  2023q1                  0         0.292             0.00262 
5 CC    2021q1                 NA         0.580            NA       
6 OLN   2021q3                  1         0.363            -0.000520
# ℹ 2 more variables: margen_operativo_p_predprob <dbl>,
#   margen_operativo_p_pred <dbl>

Ahora construyo la matriz de confusión para evaluar el modelo.

# Convierto las variables a factores
test$margen_operativo_p = factor(test$margen_operativo_p,levels=c("1","0"))
test$margen_operativo_p_pred = factor(test$margen_operativo_p_pred,levels=c("1","0"))

# Construyo la matriz de confusión
CM1<- confusionMatrix(test$margen_operativo_p,test$margen_operativo_p_pred, positive='1')
CM1
Confusion Matrix and Statistics

          Reference
Prediction  1  0
         1  0  8
         0  0 13
                                          
               Accuracy : 0.619           
                 95% CI : (0.3844, 0.8189)
    No Information Rate : 1               
    P-Value [Acc > NIR] : 1.00000         
                                          
                  Kappa : 0               
                                          
 Mcnemar's Test P-Value : 0.01333         
                                          
            Sensitivity :    NA           
            Specificity : 0.619           
         Pos Pred Value :    NA           
         Neg Pred Value :    NA           
             Prevalence : 0.000           
         Detection Rate : 0.000           
   Detection Prevalence : 0.381           
      Balanced Accuracy :    NA           
                                          
       'Positive' Class : 1               
                                          

11 Análisis de la Matriz de Confusión y Métricas

La matriz de confusión muestra que el modelo no identificó ningún verdadero positivo, con 8 falsos positivos y 13 verdaderos negativos:

Referencia (Real)
Predicción 1 0
1 0 8
0 0 13

11.1 Sensitivity (Sensibilidad)

La sensibilidad mide la capacidad del modelo para detectar verdaderos positivos. Dado que no hubo positivos reales ni predicciones correctas de la clase 1, no se puede calcular (NA). Esto refleja un problema significativo: el modelo no logra detectar ninguna instancia de la clase positiva.

11.2 Specificity (Especificidad)

La especificidad, que mide la capacidad del modelo para identificar correctamente los negativos, es de 0.619. Esto indica que el 61.9% de las veces el modelo predice correctamente la clase negativa, lo cual es moderadamente bueno.

11.3 Falsos Positivos y Falsos Negativos

El modelo presenta 8 falsos positivos (predijo 1 cuando era 0) y 0 falsos negativos (no falló al predecir ningún positivo). Los falsos positivos son un gran problema, ya que el modelo predice la clase positiva erróneamente en varias ocasiones.

11.4 Valor Predictivo Positivo (PPV) y Negativo (NPV)

Debido a la ausencia de verdaderos positivos, tanto el PPV como el NPV no se pueden calcular (NA). Esto significa que no podemos evaluar qué tan confiable es el modelo al predecir casos positivos o negativos.

11.5 Kappa

El coeficiente Kappa es 0, lo que indica que el rendimiento del modelo no es mejor que una predicción al azar. Esto sugiere que el modelo carece de utilidad para identificar correctamente ambas clases.

11.6 Problemas Clave del Modelo:

  • Incapacidad para detectar positivos: El modelo no identifica ningún verdadero positivo, lo que anula su sensibilidad.
  • Falsos positivos elevados: Los falsos positivos son una preocupación crítica, ya que el modelo frecuentemente clasifica instancias negativas como positivas.
  • Rendimiento general pobre: Con un Kappa de 0 y métricas clave no calculables, el modelo está sesgado y no es útil para la tarea de clasificación.