Este informe ha sido realizado en rmd, mediante la generación de un fichero html
Con el objetivo de diagnosticar el comportamiento de un clúster de bases de datos distribuido, se ha llevado a cabo una auditoría sobre un registro histórico de 1000 transacciones ejecutadas bajo diversas condiciones de estrés operativo, desde consultas analíticas habituales en períodos de baja demanda hasta picos de concurrencia extrema provocados por cargas masivas en lote (Batch) o tráfico transaccional intensivo (OLTP).
Como parte de un plan de optimización del sistema, se desplegó un conjunto de mejoras técnicas orientadas a maximizar la eficiencia del motor, incluyendo la reestructuración de índices, la reescritura de sentencias clave y el ajuste del buffer de memoria caché. Para medir de manera rigurosa la efectividad de estas intervenciones, se registraron y compararon las latencias y métricas clave del sistema antes y después del proceso de tuning.
El fichero DB_Optimization_DS3.csv contiene los datos
correspondientes a las variables recogidas en la monitorización del
clúster, que se describen a continuación:
| Variable | Tipo | Unidades / Valores | Descripción |
|---|---|---|---|
| ID | carácter | Código (TRX-XXXX) | Identificador único de la transacción o consulta registrada. |
| Conn_Sec | discreta | conn/s | Número de conexiones de clientes o hilos concurrentes activos por segundo. |
| RT_Base | continua | ms | Tiempo de respuesta original previo al proceso de optimización. |
| RT_Post | continua | ms | Tiempo de respuesta tras la aplicación de técnicas de tuning. |
| Cache_Hit | continua | % | Porcentaje de lecturas resueltas directamente en la memoria RAM o caché. |
| Disk_Write | continua | MB/s | Volumen de transferencia I/O de escritura en almacenamiento secundario. |
| Rows_Scanned | continua | k (miles) | Número de registros escaneados (en miles) para resolver la petición. |
| DB_Engine | atributo nominal | PostgreSQL, MySQL, MongoDB, Oracle | Tecnología o motor de base de datos donde se ejecutó la consulta. |
| Query_Type | atributo nominal | Point_Select, Range_Scan, Complex_Join, Batch_Update | Categorización operacional de la sentencia o comando ejecutado. |
| Isolation_Level | atributo ordinal | Read_Uncommitted < Read_Committed < Repeatable_Read < Serializable | Grado de aislamiento ACID de la transacción frente a concurrencia. |
| Workload_Level | atributo ordinal | Low < Medium < High < Critical | Nivel de carga de trabajo global del clúster en la ejecución. |
Enunciado Ejercicio 2.1.1: Construir la tabla de frecuencias del grado de aislamiento ACID de la transacción frente a concurrencia Isolation_Level condicionada al tipo de comando ejecutado Query_Type, y comprobar si dichas variables son independientes, mediante el test apropiado, explicar el resultado.
En primer lugar, importamos la base de datos
DB_Optimization_DS3.csv, cargamos las librerías necesarias
y declaramos cada variable, ya sea como cuantitativa discreta,
cuantitativa continua, cualitativa nominal o cualitativa ordinal.
# Establecemos el directorio de trabajo
remove(list = ls())
#setwd("") Depende del dispositivo que se use
# Importamos el fichero
DB_Optimization_DS3 <- read.csv("DB_Optimization_DS3.csv")
# Creamos una copia, para trabajar con el data frame DB
DB <- as.data.frame(DB_Optimization_DS3)
attach(DB)
# Carga de librerías
library(DescTools) # Desc(), Outlier(), LeveneTest(), DunnTest()
library(LearningStats) # indepchisq.test()
library(e1071) # skewness(), kurtosis()
# Comprobamos los textos exactos de las cualitativas antes de declararlas
unique(DB_Engine)
## [1] "MySQL" "Redis" "MongoDB" "PostgreSQL"
unique(Query_Type)
## [1] "Complex_Select" "Bulk_Insert" "Indexed_Join" "Update"
unique(Isolation_Level)
## [1] "Read Committed" "Serializable" "Repeatable Read"
unique(Workload_Level)
## [1] "Low" "Medium" "Critical" "High"
# Declaración de las variables cualitativas nominales como factores
DB$DB_Engine <- factor(DB_Engine, ordered = FALSE)
DB$Query_Type <- factor(Query_Type, ordered = FALSE)
# Declaración de las variables cualitativas ordinales como factores ordenados
DB$Isolation_Level <- factor(Isolation_Level,
levels = c("Read Uncommitted", "Read Committed",
"Repeatable Read", "Serializable"),
ordered = TRUE)
DB$Workload_Level <- factor(Workload_Level,
levels = c("Low", "Medium", "High", "Critical"),
ordered = TRUE)
# Declaración de variables cuantitativas
DB$Conn_Sec <- as.integer(DB$Conn_Sec) # discreta
DB$RT_Base <- as.numeric(DB$RT_Base) # continua
DB$RT_Post <- as.numeric(DB$RT_Post) # continua
DB$Cache_Hit <- as.numeric(DB$Cache_Hit) # continua
DB$Disk_Write <- as.numeric(DB$Disk_Write) # continua
DB$Rows_Scanned <- as.numeric(DB$Rows_Scanned) # continua
str(DB)
## 'data.frame': 1000 obs. of 10 variables:
## $ Conn_Sec : int 45 86 125 39 97 180 207 188 172 18 ...
## $ RT_Base : num 10.61 1.64 33.14 2.59 4.01 ...
## $ RT_Post : num 3.8 0.6 12.62 0.92 1.49 ...
## $ Cache_Hit : num 13.69 10.08 26.21 27.95 5.96 ...
## $ Disk_Write : num 2.15 23.54 4.72 5.56 3.64 ...
## $ Rows_Scanned : num 30.8 35.3 55 28 15.2 ...
## $ DB_Engine : Factor w/ 4 levels "MongoDB","MySQL",..: 2 4 2 1 3 1 4 2 3 3 ...
## $ Query_Type : Factor w/ 4 levels "Bulk_Insert",..: 2 1 1 2 1 3 2 4 4 4 ...
## $ Isolation_Level: Ord.factor w/ 4 levels "Read Uncommitted"<..: 2 4 4 3 3 3 3 4 4 3 ...
## $ Workload_Level : Ord.factor w/ 4 levels "Low"<"Medium"<..: 1 1 2 1 1 2 2 2 2 1 ...
# Comprobamos que no se ha generado NA
sum(is.na(DB$Isolation_Level)) # debe dar 0
## [1] 0
sum(is.na(DB$Workload_Level)) # debe dar 0
## [1] 0
# Actualizamos el attach con las variables ya tipadas
detach(DB)
attach(DB)
Para analizar la relación conjunta entre el grado de aislamiento ACID
(Isolation_Level) y el tipo de comando ejecutado
(Query_Type), construimos en primer lugar la tabla de
contingencia de frecuencias absolutas con sus marginales mediante
table() y addmargins().
# Tabla de contingencia de las variables Isolation_Level y Query_Type
table1 <- table(Isolation_Level, Query_Type)
addmargins(table1)
## Query_Type
## Isolation_Level Bulk_Insert Complex_Select Indexed_Join Update Sum
## Read Uncommitted 0 0 0 0 0
## Read Committed 66 83 85 72 306
## Repeatable Read 94 74 90 97 355
## Serializable 89 84 80 86 339
## Sum 249 241 255 255 1000
Observamos que el tamaño total de la muestra es de \(N = 1000\) transacciones, repartidas de
forma muy equilibrada entre los cuatro tipos de comando ejecutados:
Bulk_Insert (249 observaciones),
Complex_Select (241), Indexed_Join (255) y
Update (255). Además, apreciamos que el nivel de
aislamiento Read Uncommitted presenta frecuencia absoluta
nula (0 observaciones) en todos los tipos de comando ejecutados,
concentrándose la totalidad de las transacciones en los niveles
Read Committed (306), Repeatable Read (355) y
Serializable (339).
A continuación, calculamos la distribución de frecuencias relativas
de Isolation_Level condicionada a cada categoría de
Query_Type (por columnas) utilizando la función
proportions().
# Distribución de Isolation_Level condicionada a Query_Type por columnas
cond_column <- proportions(table1, "Query_Type")
round(cond_column, 3) # Redondeamos a tres cifras decimales
## Query_Type
## Isolation_Level Bulk_Insert Complex_Select Indexed_Join Update
## Read Uncommitted 0.000 0.000 0.000 0.000
## Read Committed 0.265 0.344 0.333 0.282
## Repeatable Read 0.378 0.307 0.353 0.380
## Serializable 0.357 0.349 0.314 0.337
Observamos en las distribuciones condicionadas por columnas que los porcentajes de cada nivel de aislamiento se mantienen muy estables entre los distintos tipos de consulta ejecutados:
Bulk_Insert, las proporciones son \(26,5\%\) para Read Committed,
\(37,8\%\) para
Repeatable Read y \(35,7\%\) para
Serializable.Complex_Select, se reparten en \(34,4\%\), \(30,7\%\) y \(34,9\%\), respectivamente.Indexed_Join, corresponden a \(33,3\%\), \(35,3\%\) y \(31,4\%\), mientras que en
Update son \(28,2\%\),
\(38,0\%\) y \(33,7\%\).Dado que los perfiles por columna son muy similares entre sí (oscilando en torno al \(30\% - 38\%\)) y próximos a la distribución marginal global (\(30,6\%\), \(35,5\%\) y \(33,9\%\)), existe un indicio descriptivo de que ambas variables se comportan de manera independiente.
Comprobemos formalmente si las variables cualitativas
Isolation_Level y Query_Type son
independientes mediante el test Chi-cuadrado de independencia
(indepchisq.test), planteando las siguientes hipótesis:
Isolation_Level y Query_Type son
independientes.Isolation_Level y
Query_Type.Dado que la categoría Read Uncommitted no presenta
observaciones en la muestra, la eliminamos previamente para evitar
divisiones por cero en el cálculo de las frecuencias esperadas.
# Vamos a comprobar si las variables Isolation_Level y Query_Type son independientes
# usando el test Chi-cuadrado
# Como pudimos observar anteriormente, el nivel Read Uncommitted no aparece en la muestra,
# por lo que necesitamos eliminarlo de la tabla para poder realizar correctamente
# el test Chi-cuadrado
table2 <- table1[-1, ] # Eliminamos la fila 1
# Finalmente realizamos el test Chi-cuadrado
indepchisq.test(table2, alpha = 0.05, plot = FALSE)
##
## Chi-squared test for independence
##
## H₀: X and Y are independent
## Hₐ: X and Y are not independent
## X² = ∑ᵢⱼ (nᵢⱼ - Eᵢⱼ)² / Eᵢⱼ
## X² ∈ X²_{(r-1)(s-1)}
## α = 0.05
## X²_obs = 6.79296
## RR = [12.59159, +∞)
## p-value = 0.34042
## Observed frequencies:
## Query_Type
## Isolation_Level Bulk_Insert Complex_Select Indexed_Join Update
## Read Committed 66 83 85 72
## Repeatable Read 94 74 90 97
## Serializable 89 84 80 86
## Expected frequencies:
## Bulk_Insert Complex_Select Indexed_Join Update
## Read Committed 76.19 73.75 78.03 78.03
## Repeatable Read 88.39 85.55 90.52 90.52
## Serializable 84.41 81.70 86.44 86.44
A partir de los resultados obtenidos en el contraste Chi-cuadrado de independencia (con un nivel de significación \(\alpha = 0,05\)):
Enunciado Ejercicio 2.1.2: Realizar un análisis comparativo de la variable número de registros escaneados Rows_Scanned en función de grupos determinados por el tipo de comando ejecutado Query_Type mediante los gráficos, medidas descriptivas y tests correspondientes, e interpretar de forma razonada los resultados.
Pasamos ahora al estudio comparativo de la variable cuantitativa
continua Rows_Scanned (miles de registros escaneados) según los cuatro
grupos definidos por Query_Type (Batch_Update,
Complex_Join, Point_Select y
Range_Scan).
paleta_morada <- c("#E8DAEF", "#C39BD3", "#8E44AD", "#5C068C")
# ---- 1. Grupos según el tipo de comando ----
grupos <- split(DB, DB$Query_Type) # un data frame por Query_Type
labs_q <- levels(DB$Query_Type) # etiquetas de los grupos
table(Query_Type) # tamaño de cada grupo
## Query_Type
## Bulk_Insert Complex_Select Indexed_Join Update
## 249 241 255 255
# ---- 2. Histogramas por grupo (mismos intervalos y ejes) ----
br <- seq(min(Rows_Scanned), max(Rows_Scanned), length.out = 21)
ymax <- max(sapply(grupos, function(g)
max(hist(g$Rows_Scanned, breaks = br, plot = FALSE)$density)))
par(mfrow = c(2, 2))
for (i in seq_along(grupos)) {
hist(grupos[[i]]$Rows_Scanned,
breaks = br, freq = FALSE,
xlim = range(br), ylim = c(0, ymax * 1.05),
col = paleta_morada[i], main = labs_q[i],
xlab = "Rows_Scanned (miles)")
}
par(mfrow = c(1, 1))
Figura 1: Histogramas de densidad de Rows_Scanned según el tipo de comando (Query_Type).
Calculamos los coeficientes de asimetría y curtosis, y representamos los diagramas de caja y patillas, identificando además los valores atípicos.
# ---- 3. Medidas de forma (asimetría y curtosis) ----
forma <- sapply(grupos, function(g)
c("Asimetría" = skewness(g$Rows_Scanned),
"Curtosis" = kurtosis(g$Rows_Scanned)))
round(forma, 3)
## Bulk_Insert Complex_Select Indexed_Join Update
## Asimetría 1.297 2.573 4.430 2.486
## Curtosis 1.407 10.457 33.463 8.953
# Asimetría > 0: cola a la derecha; < 0: cola a la izquierda
# Curtosis > 0: más apuntada que la normal; < 0: más aplastada
# ---- 4. Diagrama de caja y patillas ----
boxplot(Rows_Scanned ~ Query_Type, col = paleta_morada, cex.axis = 0.8,
xlab = "Tipo de comando", ylab = "Rows_Scanned (miles)")
# Versión en escala logarítmica (opcional, si algún grupo queda aplastado)
boxplot(Rows_Scanned ~ Query_Type, col = paleta_morada, log = "y",
xlab = "Tipo de comando", ylab = "Rows_Scanned (miles, escala log)")
# Datos atípicos por grupo y su número
atip <- tapply(Rows_Scanned, Query_Type, function(x) sort(Outlier(x)))
atip
## $Bulk_Insert
## [1] 147.12 150.74 151.16 153.69 167.45 174.46 177.76 192.11
##
## $Complex_Select
## [1] 410.00 420.33 451.59 463.00 467.24 467.48 481.90 483.59 489.96 629.80
## [11] 904.47
##
## $Indexed_Join
## [1] 262.61 272.71 280.63 281.83 289.20 294.46 326.63 332.10 353.90
## [10] 369.82 399.17 507.23 593.40 1116.94
##
## $Update
## [1] 172.23 173.87 178.23 181.12 186.56 192.70 192.71 194.82 198.16 208.43
## [11] 218.60 232.11 234.40 243.87 266.53 286.08 294.93 314.14 344.89 489.00
lengths(atip)
## Bulk_Insert Complex_Select Indexed_Join Update
## 8 11 14 20
Figura 2: Diagramas de caja y patillas de Rows_Scanned por Query_Type (escala lineal y logarítmica).
Como podemos ver en las Figuras 1 y 2, así como en las medidas de forma:
Point_Select) se concentran en valores muy bajos
de Rows_Scanned, mientras que operaciones más complejas
como Range_Scan, Batch_Update o
Complex_Join desplazan sus distribuciones hacia valores
superiores y presentan mayor dispersión.Calculamos las medidas de posición y dispersión por grupo y aplicamos
la batería de contrastes de hipótesis para determinar si existen
diferencias significativas en Rows_Scanned entre los tipos
de comando:
Rows_Scanned es idéntica en los cuatro
grupos.# ---- 5. Medidas de posición y dispersión por grupo ----
desc <- t(sapply(grupos, function(g) {
x <- g$Rows_Scanned
c(summary(x), IQR = IQR(x), SD = sd(x), CV = sd(x) / mean(x))
}))
round(desc, 3)
## Min. 1st Qu. Median Mean 3rd Qu. Max. IQR SD CV
## Bulk_Insert 1.51 18.16 35.50 47.242 68.13 192.11 49.97 38.088 0.806
## Complex_Select 4.54 50.65 96.63 127.853 176.05 904.47 125.40 114.489 0.895
## Indexed_Join 5.00 39.19 72.14 101.581 126.28 1116.94 87.09 106.240 1.046
## Update 1.84 28.23 51.72 69.124 83.86 489.00 55.63 64.565 0.934
# ---- 6. Resumen por grupos + test de Kruskal-Wallis (DescTools) ----
Desc(Rows_Scanned ~ Query_Type, DB, digits = 2, verbose = c(3), plotit = TRUE)
## ──────────────────────────────────────────────────────────────────────────────
## Rows_Scanned ~ Query_Type (DB)
##
## Summary:
## n pairs: 1'000, valid: 1'000 (100.0%), missings: 0 (0.0%), groups: 4
##
##
## Bulk_Insert Complex_Select Indexed_Join Update
## mean 47.24 127.85 101.58 69.12
## median 35.50 96.63 72.14 51.72
## sd 38.09 114.49 106.24 64.56
## IQR 49.97 125.40 87.09 55.63
## n 249 241 255 255
## np 24.90% 24.10% 25.50% 25.50%
## NAs 0 0 0 0
## 0s 0 0 0 0
## min 1.51 4.54 5.00 1.84
## max 192.11 904.47 1'116.94 489.00
## Q1 18.16 50.65 39.19 28.23
## Q3 68.13 176.05 126.28 83.86
## mad 30.33 78.40 60.46 38.31
## skew 1.30 2.57 4.43 2.49
## kurt 1.41 10.46 33.46 8.95
##
## Kruskal-Wallis rank sum test:
## Kruskal-Wallis chi-squared = 153.43, df = 3, p-value < 2.2e-16
# ---- 7. Contrastes de hipótesis ----
# 7.1 Normalidad en cada grupo (Shapiro-Wilk). H0: los datos son normales
sapply(grupos, function(g) shapiro.test(g$Rows_Scanned)$p.value)
## Bulk_Insert Complex_Select Indexed_Join Update
## 1.889739e-13 6.016420e-18 2.363577e-22 8.048963e-19
# 7.2 Homogeneidad de varianzas (Levene). H0: varianzas iguales
LeveneTest(Rows_Scanned ~ Query_Type, data = DB)
## Levene's Test for Homogeneity of Variance (center = median)
## Df F value Pr(>F)
## group 3 21.684 1.312e-13 ***
## 996
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# 7.3 Kruskal-Wallis. H0: misma distribución de Rows_Scanned en todos los grupos
kruskal.test(Rows_Scanned ~ Query_Type, data = DB)
##
## Kruskal-Wallis rank sum test
##
## data: Rows_Scanned by Query_Type
## Kruskal-Wallis chi-squared = 153.43, df = 3, p-value < 2.2e-16
# 7.4 Comparaciones por pares (test de Dunn, ajuste de Holm)
DunnTest(Rows_Scanned ~ Query_Type, data = DB, method = "holm")
##
## Dunn's test of multiple comparisons using rank sums : holm
##
## mean.rank.diff pval
## Complex_Select-Bulk_Insert 300.85026 < 2e-16 ***
## Indexed_Join-Bulk_Insert 218.60761 < 2e-16 ***
## Update-Bulk_Insert 104.20957 0.0001 ***
## Indexed_Join-Complex_Select -82.24266 0.0015 **
## Update-Complex_Select -196.64070 1.4e-13 ***
## Update-Indexed_Join -114.39804 2.3e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Liberamos el data frame al finalizar los ejercicios 2.1.1 y 2.1.2
detach(DB)
Enunciado Ejercicio 2.2.3: Realizar un análisis de regresión lineal entre la variable número de registros escaneados Rows_Scanned y número de conexiones de clientes o hilos concurrentes activos por segundo Conn_Sec para las ejecuciones de actualización, Query_Type = Update, comprobar las hipótesis necesarias, y evaluar la bondad del ajuste del modelo.
#En primer lugar filtramos el data frame las variables Rows_Scanned y número de #conexiones de clientes o hilos concurrentes activos por segundo Conn_Sec para las #ejecuciones de actualización, Query_Type = Update
DB_Optimization_DS3 <- read.csv("DB_Optimization_DS3.csv")
attach(DB_Optimization_DS3)
data_filtrado <- subset(DB_Optimization_DS3, Query_Type == "Update")
detach(DB_Optimization_DS3)
Calculamos el coeficiente de correlación de las variables Rows_Scanned y Conn_Sec filtradas, para ello usamos:
La función cor del paquete stats: nos
devuelve la matríz de correlaciones entre cada par de un conjunto de
variables o de sólo dos de ellas.
La función rcorr del paquete Hmisc que,
adenás de la correlación, aplica un tests para contrastar si dichos
coeficientes son significactivos.
Ambas funciones calculan el coeficiente de correlación de Pearson por defecto.
cor(data_filtrado$Rows_Scanned, data_filtrado$Conn_Sec) # matriz de correlación básica
## [1] 0.7534501
#Para la función rcorr:
library(Hmisc)
rcorr(data_filtrado$Rows_Scanned, data_filtrado$Conn_Sec)
## x y
## x 1.00 0.75
## y 0.75 1.00
##
## n= 255
##
##
## P
## x y
## x 0
## y 0
Observamos que coeficiente de correlación de Pearson es de 0,75, lo
que indica que existe una correlación lineal positiva relativamente
fuerte entre el número de conexiones por segundo Conn_Secy
el número de registros escaneados Rows_Scanned para las
consultas de actualización.
Esto significa que, en general, cuando aumenta el número de conexiones por segundo, también tiende a aumentar el número de registros escaneados.
Representamos entonces el gráfico de dispersión o nube de puntos
entre ambas variables: Conn_Sec y Rows_Scanned, considerando únicamente
las ejecuciones de actualización
(Query_Type = "Update").
plot(data_filtrado$Conn_Sec, data_filtrado$Rows_Scanned,
xlab = "Conexiones por segundo",
ylab = "Registros escaneados",
main = "Diagrama de dispersión")
Figura 3: Gráfico de Dispersión entre Conn_Sec y Rows_Scanned.
Como podemos ver en la figura, existe una tendencia lineal positiva entre ambas variables. Además, se observa que la dispersión de los puntos aumenta a medida que crecen los valores de las variables y aparecen algunos posibles casos atípicos.
Decidimos realizar un ajuste a un modelo lineal entre las variables Rows_Scanned y Conn_Sec, pero solo para las ejecuciones de actualización (Query_Type = “Update”), que se corresponden con la nube de puntos representada anteriormente.
#usamos lm del paquete Stats
modelo_lineal <- lm(Rows_Scanned ~ Conn_Sec, data=data_filtrado) # Ajusta el modelo lineal
modelo_lineal # Devuelve sólo el modelo con sus coeficientes
##
## Call:
## lm(formula = Rows_Scanned ~ Conn_Sec, data = data_filtrado)
##
## Coefficients:
## (Intercept) Conn_Sec
## -6.3983 0.7058
summary(modelo_lineal)
##
## Call:
## lm(formula = Rows_Scanned ~ Conn_Sec, data = data_filtrado)
##
## Residuals:
## Min 1Q Median 3Q Max
## -192.519 -20.606 -1.317 14.149 241.315
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6.39826 4.92575 -1.299 0.195
## Conn_Sec 0.70579 0.03872 18.227 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 42.54 on 253 degrees of freedom
## Multiple R-squared: 0.5677, Adjusted R-squared: 0.566
## F-statistic: 332.2 on 1 and 253 DF, p-value: < 2.2e-16
Observamos que se rechaza la hipótesis nula de que no existe relación lineal entre ambas variables, ya que el \(p\text{-valor}\) es menor que 0,05.
Además, el coeficiente de determinación es de 0,5677, por lo que el
modelo explica aproximadamente el 56,77 % de la variabilidad de
Rows_Scanned.
También observamos que la pendiente es positiva (0,70579), por lo
que, a medida que aumenta Conn_Sec, también tiende a
aumentar Rows_Scanned.
También observamos que existen algunos residuos bastante grandes, llegando hasta −192,519 y 241,315, lo que indica que el modelo presenta errores de predicción elevados en algunas observaciones.
Comprobemos si se cumplen las hipótesis necesarias para poder realizar la regresión lineal.
Normalidad de la variable dependiente Y para valores fijos de X.
Homocedasticidad o igualdad de varianzas de la variable respuesta Y para valores fijos de X
Independencia de las observaciones de la variable Y para valores fijos de X
Linealidad entre la variable respuesta Y, y la variable regresora o explicativa X
Como la función lm genera cuatro gráficos de diagnóstico
los representamos mediante la función plot, y que nos
permiten comprobar si el modelo que hemos ajustado verifica las
hipótesis de la regresión.
par(mfrow=c(2,2)) #permite ver los graficos de dos en dos
plot(modelo_lineal)
Figura 4: Gráficos de Diagnósticos.
El gráfico Q-Q de la Figura 4 muestra los residuos estandarizados frente a los cuantiles de la distribución normal. Si los residuos siguen una distribución normal, los puntos deberían ajustarse aproximadamente a la línea diagonal. Se observa que para valores entre -1.5 y 1.5 los puntos parecen ajustarse pero para comprobarlo, utilizaremos el test de Shapiro-Wilk.
er <- residuals(modelo_lineal) # Extraemos los residuos
sh <- shapiro.test(er) # Aplicamos Shapiro-Wilk
sh
##
## Shapiro-Wilk normality test
##
## data: er
## W = 0.86066, p-value = 1.982e-14
El valor del estadístico \(W=0.86066\) y el \(p\text{-valor}=1.982 \times 10^{-14}\). Como es menor que \(\alpha=0.1\), se rechaza la hipótesis nula \(H_0: e_{ij}\sim N(0,\sigma)\), es decir, que podemos considerar que los residuos no siguen una distribución normal.
El gráfico Scale-Location de la Figura 4 nos permite comprobar si la varianza de los residuos es constante. Si la línea roja es aproximadamente horizontal y los puntos no presentan ningún patrón, podemos considerar que la varianza es constante. En nuestro caso, observamos que la línea roja no parece ser horizontal y los puntos parecen seguir cierto patrón (se concentran al incio del gráfico), por lo que la varianza podría no ser constante. Para comprobarlo, utilizaremos el test de Breusch-Pagan.
library(lmtest)# Cargamos el paquete
bptest(modelo_lineal) # Aplica el test de Breusch-Pagan
##
## studentized Breusch-Pagan test
##
## data: modelo_lineal
## BP = 60.877, df = 1, p-value = 6.077e-15
El estadístico del test de Breusch-Pagan toma el valor \(BP = 60.877\) con \(p\text{-valor} = 6.077 \times 10^{-15} < 0.1\), por tanto, rechazamos la hipótesis nula de igualdad de varianzas.
El gráfico Residuals vs Fitted de la Figura 4 nos permite comprobar si el modelo lineal es adecuado. Si la línea roja es aproximadamente horizontal y los puntos se distribuyen alrededor del cero sin seguir ningún patrón, podemos considerar que el ajuste es adecuado. Observamos que la línea roja parece aproximadamente horizontal, pero existe una concentración de puntos al inicio del gráfico, lo que podría indicar algún problema en el ajuste. Para comprobar la independencia de los residuos, utilizaremos el test de Durbin-Watson.
dwtest(modelo_lineal, alternative="two.sided") # Se aplica el test de Durbin Watson
##
## Durbin-Watson test
##
## data: modelo_lineal
## DW = 2.0073, p-value = 0.9567
## alternative hypothesis: true autocorrelation is not 0
El estadístico del test de Durbin-Watson toma el valor \(DW=2.0073\) con \(p\text{-valor}=0.9567>0.1\), por tanto, aceptamos la hipótesis nula de independencia, es decir, podemos considerar que los residuos son independientes.
Por último, el gráfico Residuals vs Leverage permite detectar posibles datos atípicos o influyentes que pueden afectar al modelo.
Para realizar las predicciones de nuestro modelo lineal, utilizaremos
la función predict() de R.
bandas.frame <- data.frame(Conn_Sec = 1:450) # Nuevos valores de la variable explicativa
bc <- predict(modelo_lineal, interval = "confidence", newdata = bandas.frame) # Banda de confianza
bp <- predict(modelo_lineal, interval = "prediction", newdata = bandas.frame) # Banda de predicción
plot(data_filtrado$Conn_Sec, data_filtrado$Rows_Scanned,
xlab = "Conn_Sec", ylab = "Rows_Scanned") # Diagrama de dispersión
matlines(bandas.frame$Conn_Sec, bc,
lty = c(1,2,2), col = c("green","blue","blue"))
matlines(bandas.frame$Conn_Sec, bp,
lty = c(0,3,3), col = "red")
legend("topleft",
legend = c("Fit","Confidence","Prediction"),
col = c("green","blue","red"), lty = 1:3)
Figura 5: Ajuste del modelo con bandas de confianza y predicción.
Observamos en la figura 5 que existe una tendencia lineal positiva entre ambas variables. La banda de confianza (azul) se encuentra próxima a la recta de regresión, mientras que la banda de predicción (roja) es más amplia. Además, observamos que algunos puntos quedan fuera de la banda de predicción.
A partir de los análisis estadísticos realizados sobre la auditoría
del clúster de bases de datos (DB_Optimization_DS3.csv),
extraemos las siguientes conclusiones generales:
Read Uncommitted no se utiliza en ninguna de las 1000
transacciones registradas. Mediante las distribuciones condicionadas y
el test \(\chi^2\) de independencia,
evaluamos si la elección del grado de aislamiento ACID
(Read Committed, Repeatable Read o
Serializable) depende del tipo de sentencia ejecutada
(Query_Type), contrastando formalmente la existencia o
ausencia de asociación significativa entre ambas variables
cualitativas.Rows_Scanned),
incumpliendo los supuestos paramétricos de normalidad (Shapiro-Wilk) y
homocedasticidad (Levene). Tanto el test no paramétrico de
Kruskal-Wallis como las comparaciones post-hoc de Dunn confirman
diferencias estadísticamente significativas entre los distintos tipos de
consulta, siendo las consultas puntuales (Point_Select) las
que escanean un menor volumen de registros frente a operaciones más
exigentes (Range_Scan, Batch_Update y
Complex_Join).Conn_Sec y Rows_Scanned, aunque
el modelo solo explica el 56,77 % de la variabilidad de los registros
escaneados. Además, observamos que no se cumplen las hipótesis de
normalidad y de igualdad de varianzas de los residuos. Por tanto, aunque
existe una relación lineal entre ambas variables, el modelo presenta
algunas limitaciones y no podemos considerar que el ajuste sea
completamente adecuado.Trabajo de cada uno Besay: se encargó del apartado 1 del ejercicio 2.1 Joaquín: se encargó del apartado 2 del ejercicio 2.1 Yuri: se encargó del ejercicio 2.2 Alfonso y Álvaro: se encargaron de recopilar el código y realizar el informe en rmd.