library(readxl)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.0     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)
library(lubridate)
library(ggplot2)

df <- read.csv("Documents/r studio/regional_price_dataset.csv", header = TRUE, stringsAsFactors = FALSE)
df$fecha <- as.Date(df$fecha)
# Columna ingreso
df <- df %>% mutate(ingreso = precio * cantidad)
# Ver registros
head(df)
##        fecha   zona precio cantidad ingreso
## 1 2018-01-01 Centro   13.7      195  2671.5
## 2 2018-01-01  Norte   13.7      149  2041.3
## 3 2018-01-01    Sur   13.7      168  2301.6
## 4 2018-01-02 Centro   13.7      221  3027.7
## 5 2018-01-02  Norte   13.7      179  2452.3
## 6 2018-01-02    Sur   13.7      183  2507.1

I. Análisis de Datos de Series de Tiempo

Serie de Tiempo de Cantidad por Región (Datos Semanales)

# Cargar las librerías necesarias
library(ggplot2)
library(dplyr)
library(lubridate)

# Convertir la fecha a formato Date si aún no lo está
df <- df %>%
  mutate(fecha = as.Date(fecha)) 

# Agrupar por semana y zona, sumando la cantidad vendida
data_semanal <- df %>%
  mutate(semana = floor_date(fecha, "week", week_start = 1)) %>%  # Asegurar que la semana empieza el lunes
  group_by(semana, zona) %>%
  summarise(cantidad = sum(cantidad), .groups = "drop") %>%
  arrange(semana)  # Ordenar por fecha

# Crear el gráfico de líneas
ggplot(data_semanal, aes(x = semana, y = cantidad, color = zona, group = zona)) +
  geom_line(size = 0.5) +  # Hacer las líneas más visibles
  labs(title = "Serie de Tiempo de Cantidad por Región (Datos Semanales)",
       x = "Fecha (Semana)",
       y = "Cantidad Vendida") +
  theme_minimal() +
  scale_color_manual(values = c("Centro" = "#1f77b4", "Norte" = "#ff7f0e", "Sur" = "#2ca02c")) +  # Colores similares a la imagen
  scale_x_date(date_labels = "%Y-%m", date_breaks = "1 month") + 
  scale_y_continuous(breaks = seq(600, 2200, by = 200)) +  # Marcas cada mes
  theme(axis.text.x = element_text(angle = 45, hjust = 1))  # Mejorar la legibilidad del eje X
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Distribución de Cantidad por Día de la Semana y Zona

# Convertir la columna 'fecha' a tipo Date (asegúrate de que ya lo hiciste)
df$fecha <- as.Date(df$fecha)

# Extraer el día de la semana
df$dia_semana <- wday(df$fecha, label = TRUE, week_start = 1) # Lunes como inicio de semana

# Visualizar la distribución de cantidad por día de la semana, con zonas en el mismo gráfico
ggplot(df, aes(x = dia_semana, y = cantidad, fill = zona)) +
  geom_boxplot(alpha = 0.7, linewidth = 0.2) +
  scale_fill_manual(values = c("Centro" = "#E41A1C", "Norte" = "#4DAF4A", "Sur" = "#377EB8")) + # Colores similares al ejemplo
  scale_y_continuous(breaks = seq(100, 400, by = 50)) + # Definir los valores del eje Y
  labs(title = "Distribución de Cantidad por Día de la Semana y Zona",
       x = "Día de la Semana",
       y = "Cantidad",
       fill = "Zona") +
  theme_bw() + # fondo gris y líneas
  theme(legend.position = "right", # Posicionar la leyenda a la derecha
        legend.background = element_rect(fill = "white", colour = "black"), # Fondo blanco y borde negro para la leyenda
        legend.key.size = unit(0.5, "cm"), # Ajustar el tamaño de los cuadrados de la leyenda
        legend.text = element_text(size = 10), # Ajustar el tamaño del texto de la leyenda
        plot.title = element_text(hjust = 0.5),
        axis.title.x = element_text(face = "bold"),
        axis.title.y = element_text(face = "bold"),
        panel.grid.major = element_blank(), # Eliminar las líneas de la cuadrícula principales
        panel.grid.minor = element_blank(), # Eliminar las líneas de la cuadrícula secundarias
        panel.border = element_rect(colour = "black", fill = NA)) # Añadir un borde negro alrededor del panel

En este gráfico vemos cómo varían las ventas de un producto durante la semana en diferentes zonas. En general, parece que se vende más los fines de semana que entre semana, pero esto es más marcado en algunas zonas que en otras. Por ejemplo, en el Centro”las ventas suben mucho el sábado y domingo, mientras que en el Sur las ventas son más parecidas todos los días. También vemos que la cantidad de ventas varía bastante de un día a otro, y esta variación también es diferente en cada zona.

# Agrupar y calcular la cantidad promedio vendida por día de la semana (todas las zonas)
df_dia <- df %>%
  group_by(dia_semana) %>%
  summarise(cantidad_promedio = mean(cantidad))

# Visualizador: barras por día de la semana
ggplot(df_dia, aes(x = dia_semana, y = cantidad_promedio)) +
  geom_col(fill = "steelblue") +
  labs(
    title = "Promedio de ventas por día de la semana (todas las zonas)",
    x = "Día de la semana",
    y = "Cantidad promedio vendida"
  ) +
  theme_minimal()

# Calcular cantidad promedio por día y zona
df_dia_zona <- df %>%
  group_by(zona, dia_semana) %>%
  summarise(cantidad_promedio = mean(cantidad), .groups = "drop")

# Visualizador: barras por día y zona
ggplot(df_dia_zona, aes(x = dia_semana, y = cantidad_promedio, fill = zona)) +
  geom_col(position = "dodge") +
  labs(
    title = "Promedio de ventas por día de la semana por zona",
    x = "Día de la semana",
    y = "Cantidad promedio vendida"
  ) +
  theme_minimal()

II. Modelo de Demanda

Relación entre Precio y Ventas

library(dplyr)
library(ggplot2)
library(lubridate)

# Agrupar por semana y zona
data_semana <- df %>%
  mutate(semana = floor_date(fecha, unit = "week")) %>%
  group_by(semana, zona) %>%
  summarise(
    cantidad = sum(cantidad, na.rm = TRUE),
    precio = median(precio, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  filter(cantidad >= 400)  # <- aquí filtramos los outliers de la izquierda

# Gráfico sin los outliers
ggplot(data_semana, aes(x = cantidad, y = precio, color = zona)) + 
  geom_point(size = 2, alpha = 0.8) + 
  labs(
    title = "Cantidad de Ventas por Semana y Zona",
    x = "Cantidad de Ventas (semanal)",
    y = "Precio",
    color = "Zona"
  ) +
  theme_bw()

En resumen, este gráfico sugiere que existe una relación inversa entre el precio y la cantidad de ventas. En general, se puede observar una tendencia a que a mayor cantidad de ventas, el precio tiende a ser menor. Esto sugiere una posible relación de demanda, donde se vende más a precios más bajos.

Modelo de Demanda Considerando Una de las Zonas

library(jtools)

# Asegúrate de que zona sea factor
df$zona <- as.factor(df$zona)

# Corre la regresión
modelo <- lm(cantidad ~ precio * zona, data = df)

# Muestra el resumen con todos los elementos (coef, t, p, IC, R2, AIC, BIC...)
summ(modelo, confint = TRUE, digits = 3)
Observations 1491
Dependent variable cantidad
Type OLS linear regression
F(5,1485) 414.902
0.583
Adj. R² 0.581
Est. 2.5% 97.5% t val. p
(Intercept) 299.671 289.176 310.166 56.012 0.000
precio -7.141 -8.018 -6.264 -15.968 0.000
zonaNorte 61.204 46.363 76.046 8.089 0.000
zonaSur -62.495 -77.337 -47.654 -8.260 0.000
precio:zonaNorte -6.670 -7.911 -5.429 -10.546 0.000
precio:zonaSur 1.066 -0.175 2.306 1.685 0.092
Standard errors: OLS
# Modelo lineal por zona
ggplot(df, aes(x = precio, y = cantidad, color = zona)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~ zona) +
  labs(
    title = "Modelos de demanda por zona (regresión lineal)",
    x = "Precio",
    y = "Cantidad vendida"
  ) +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

III. Precio Elasticidad

library(dplyr)

# 1. Asegurar que zona sea factor
df$zona <- as.factor(df$zona)

# 2. Ajustar modelo con interacción
modelo <- lm(cantidad ~ precio * zona, data = df)

# 3. Extraer coeficientes del modelo
coefs <- coef(modelo)

# 4. Calcular pendiente (dQ/dP) para cada zona
pendiente_centro <- coefs["precio"]
pendiente_norte  <- coefs["precio"] + coefs["precio:zonaNorte"]
pendiente_sur    <- coefs["precio"] + coefs["precio:zonaSur"]

# 5. Calcular promedio de precio y cantidad por zona
promedios <- df %>%
  group_by(zona) %>%
  summarise(
    precio_prom = mean(precio),
    cantidad_prom = mean(cantidad),
    .groups = "drop"
  )

# 6. Calcular elasticidad para cada zona
elasticidad_df <- promedios %>%
  mutate(
    pendiente = case_when(
      zona == "Centro" ~ pendiente_centro,
      zona == "Norte"  ~ pendiente_norte,
      zona == "Sur"    ~ pendiente_sur
    ),
    elasticidad = pendiente * precio_prom / cantidad_prom
  )
library(ggplot2)

ggplot(elasticidad_df, aes(x = zona, y = elasticidad, fill = zona)) +
  geom_col() +
  geom_hline(yintercept = -1, linetype = "dashed", color = "red") +
  geom_hline(yintercept = -0.5, linetype = "dashed", color = "purple") +
  labs(
    title = "Elasticidad precio de la demanda por zona (basado en regresión)",
    x = "Zona",
    y = "Elasticidad"
  ) +
  theme_minimal()

La demanda de los productos es inelastica en las tres zonas, pero la ineslasticidad es mas fuerte en el Norte. Esto nos da a entender que la empresa puede modificar sus precios en estas zonas con mayor flexibilidad sin experimentar una caida en la cantidad de ventas.

IV. Precios & Ingresos Óptimos

library(dplyr)
library(purrr)
library(ggplot2)

# Extraer coeficientes del modelo
coefs <- coef(modelo)

# Crear funciones de demanda por zona
f_demand <- list(
  Centro = function(p) coefs["(Intercept)"] + coefs["precio"] * p,
  Norte  = function(p) {
    coefs["(Intercept)"] + coefs["zonaNorte"] +
      (coefs["precio"] + coefs["precio:zonaNorte"]) * p
  },
  Sur = function(p) {
    coefs["(Intercept)"] + coefs["zonaSur"] +
      (coefs["precio"] + coefs["precio:zonaSur"]) * p
  }
)

# Función para ingreso total
ingreso_total <- function(f_q, p) {
  q <- f_q(p)
  ingreso <- p * q
  return(ingreso)
}

# Calcular precios e ingresos óptimos
resultados <- map_dfr(names(f_demand), function(z) {
  opt <- optimize(function(p) -ingreso_total(f_demand[[z]], p), interval = c(0.01, 100))
  precio_optimo <- opt$minimum
  cantidad_optima <- f_demand[[z]](precio_optimo)
  ingreso_optimo <- precio_optimo * cantidad_optima
  data.frame(
    zona = z,
    precio_optimo = round(precio_optimo, 2),
    cantidad_optima = round(cantidad_optima, 2),
    ingreso_optimo = round(ingreso_optimo, 2)
  )
})

# Mostrar resultados en consola
print(resultados)
##                   zona precio_optimo cantidad_optima ingreso_optimo
## (Intercept)...1 Centro         20.98          149.84        3143.82
## (Intercept)...2  Norte         13.06          180.44        2357.36
## (Intercept)...3    Sur         19.52          118.59        2314.76
library(ggplot2)
library(dplyr)
library(purrr)

# Coeficientes del modelo
coefs <- coef(modelo)

# Funciones de demanda por zona
f_demand <- list(
  Centro = function(p) coefs["(Intercept)"] + coefs["precio"] * p,
  Norte  = function(p) {
    coefs["(Intercept)"] + coefs["zonaNorte"] +
      (coefs["precio"] + coefs["precio:zonaNorte"]) * p
  },
  Sur = function(p) {
    coefs["(Intercept)"] + coefs["zonaSur"] +
      (coefs["precio"] + coefs["precio:zonaSur"]) * p
  }
)

# Función de ingreso
ingreso_total <- function(f_q, p) {
  p * f_q(p)
}

# Calcular precios e ingresos óptimos
resultados <- map_dfr(names(f_demand), function(z) {
  opt <- optimize(function(p) -ingreso_total(f_demand[[z]], p), interval = c(0.01, 22.5))
  precio_optimo <- opt$minimum
  cantidad_optima <- f_demand[[z]](precio_optimo)
  ingreso_optimo <- precio_optimo * cantidad_optima
  data.frame(
    zona = z,
    precio_optimo = precio_optimo,
    ingreso_optimo = ingreso_optimo
  )
})

# Generar precios solo hasta 22.5
precios <- seq(0.01, 22.5, length.out = 500)

# Crear dataset de ingresos positivos
df_ingresos <- map_dfr(names(f_demand), function(z) {
  ingreso <- precios * f_demand[[z]](precios)
  data.frame(
    precio = precios,
    ingreso = ingreso,
    zona = z
  ) %>% filter(ingreso > 0)
})

# Graficar
ggplot(df_ingresos, aes(x = precio, y = ingreso, color = zona)) +
  geom_line(size = 1.1) +
  geom_vline(data = resultados, aes(xintercept = precio_optimo, color = zona), linetype = "dashed") +
  geom_point(data = resultados, aes(x = precio_optimo, y = ingreso_optimo, color = zona), size = 3) +
  scale_x_continuous(limits = c(0, 22.5)) +
  labs(
    title = "Curvas de ingreso total por zona",
    subtitle = "Precios hasta 22.5 y solo ingresos positivos",
    x = "Precio",
    y = "Ingreso total"
  ) +
  theme_minimal()

La gráfica nos demuestra que para el centro el precio optimo puede ser 20.98, para el norte 13.06 y para el sur 19.52. Esta información es importante para poder hacer una fijación de precios apropiada a cada mercado.

V. Responde Brevemente las siguientes preguntas de anàlisis

a. Qué es optimización de precios?

La optimización de precios basandonos en la inteligencia de negocios es el proceso de utilizar datos y herramientas de BI para determinar los precios más rentables para productos o sericios.

b. ¿Cuál es la relación entre precio elasticidad y optimización de precios? Permite que las empresas entiendan como reaccionan sus clientes a los cambios de precios y por lo tanto les ayuda a establecer orecios que maximicen los ingresos.

c. ¿Cuál es el propósito de la optimización de precios por zona? La optimización de precios por zona permite a las empresas ser más estratégicas y eficientes en su fijación de precios, dependiente de los mercados geograficos para poder alcanzar sus objetivos financieros.