Objetivo principal: Analizar las diferencias en los niveles de Calcio (CaMol), Fosforo (PhoMol) y Fosfatasa Alcalina (ALP) para pacientes mayores de 65 años de edad en función del género (Sex = Male or Female).
Objetivo específico: Determinar si la variación de las condiciones analíticas entre laboratorios o la edad de los pacientes, afecta a la distribución de las 3 variables de estudio.
Calcio=read_excel("calcium.xls") # Lectura de Datos en R
str(Calcio) # Verificar la estructura de las variables
## tibble [178 × 8] (S3: tbl_df/tbl/data.frame)
## $ Observacion: num [1:178] 1 2 3 4 5 6 7 8 9 10 ...
## $ Age : chr [1:178] "78" "72" "72" "NA" ...
## $ Sex : chr [1:178] "2" "2" "2" "2" ...
## $ ALP : chr [1:178] "83" "117" "132" "102" ...
## $ Lab : chr [1:178] "4" "4" "4" "4" ...
## $ CaMol : chr [1:178] "2.5299999999999998" "2.5" "2.4300000000000002" "2.48" ...
## $ PhoMol : chr [1:178] "1.0700000000000001" "1.1599999999999999" "1.1299999999999999" "0.81000000000000005" ...
## $ AgeG : chr [1:178] "75 - 79" "70 - 74" "70 - 74" "70 - 74" ...
attach(Calcio)
En la salida anterior se evidencia que todas la variables están siendo tomadas como caracteres, entonces se procede a cambiar su estructura, de tal forma que las variables cualitativas queden como variables de tipo factor y las variables cualitativas queden de tipo numérico.
#Se suprimen las salidas de alertas referentes a datos faltantes, pues estos se tocarán más adelante.
Calcio$Age<-suppressWarnings(as.numeric(Calcio$Age))
Calcio$ALP<-suppressWarnings(as.numeric(Calcio$ALP))
Calcio$CaMol<-suppressWarnings(as.numeric(Calcio$CaMol))
Calcio$PhoMol<-suppressWarnings(as.numeric(Calcio$PhoMol))
Calcio$Sex<-as.factor(Calcio$Sex)
Calcio$Lab<-as.factor(Calcio$Lab)
Calcio$AgeG<-as.factor(Calcio$AgeG)
Se vuelve a verificar la estructura de la base de datos, confirmando así que esta ha quedado correctamente especificada (las variables cualitativas tomadas como factor y las cuantitativas tomadas como númerico).
str(Calcio)
## tibble [178 × 8] (S3: tbl_df/tbl/data.frame)
## $ Observacion: num [1:178] 1 2 3 4 5 6 7 8 9 10 ...
## $ Age : num [1:178] 78 72 72 NA 73 73 65 68 89 84 ...
## $ Sex : Factor w/ 9 levels "1","12","2","21",..: 3 3 3 3 6 3 3 3 9 1 ...
## $ ALP : num [1:178] 83 117 132 102 114 88 213 153 86 108 ...
## $ Lab : Factor w/ 9 levels "1","2","21","3",..: 5 5 5 5 5 6 5 5 5 5 ...
## $ CaMol : num [1:178] 2.53 2.5 2.43 2.48 2.33 2.13 2.55 2.45 2.25 2.43 ...
## $ PhoMol : num [1:178] 1.07 1.16 1.13 0.81 1.13 0.84 1.26 1.23 0.65 0.84 ...
## $ AgeG : Factor w/ 7 levels "65 - 69","70 - 74",..: 3 2 2 2 2 2 6 1 6 4 ...
Para verificar que cada variable ha sido correctamente digitada en la base de datos o, que dicho dato es coherente con los rangos de la variable, se medira la consistencia de la base de datos; lo anterior con los rangos o factores especificados por el mismo artículo. En el caso de las variables CaMol y PhoMol que cuentan con varios rangos de referencia, se tomará el valor mínimo y máximo de las referncias globales para establecer el rango. Finalmente, las reglas de consistencia serán:
Variables cuantitativas:
\(65\leq Age \leq 89\)
\(2.1\leq CaMol \leq 2.7\)
\(0.84\leq PhoMol \leq 1.5\)
\(30\leq ALP \leq 115\)
Variables cualitativas:
\(Sex=\left \{ 1=Male; 2=Female \right \}\)
\(Lab=\left \{ 1=Metpath; 2=Deyor; 3=St. Elizabeth's; 4=CB Rouche; 5=YOH; 6=Horizon\right \}\)
\(AgeG=\left \{ 65-69; 70-74; 75-79; 80-84; 85-89 \right \}\)
Aplicando las reglas de consistencia a la base de datos se tiene:
# Carga del archivo de reglas de validación
Rules = editrules::editfile("consistencia.txt")
# Conexión entre las reglas
plot(Rules)
# Verificación de las reglas sobres los datos
Valid_Data = editrules::violatedEdits(Rules, Calcio)
summary(Valid_Data)
## Edit violations, 178 observations, 0 completely missing (0%):
##
## editname freq rel
## num8 34 19.1%
## num4 15 8.4%
## dat11 8 4.5%
## num5 7 3.9%
## num6 5 2.8%
## num2 3 1.7%
## dat10 3 1.7%
## mix13 3 1.7%
## num3 2 1.1%
## dat9 2 1.1%
## num7 1 0.6%
## mix9 1 0.6%
##
## Edit violations per record:
##
## errors freq rel
## 0 105 59%
## 1 54 30.3%
## 2 14 7.9%
## 3 2 1.1%
## 6 1 0.6%
## 7 2 1.1%
Del resumen estadístico de la verificación de las reglas sobre los datos se puede extraer que:
Tres pacientes presentan una edad mayor de 89 años (uno de ellos tiene una edad de 771 años).
Dos pacientes presentan una medición del calcio menor a 2.1 (cercano a 1, violando el límie inferior del rango coherente según las referencias). Quince pacientes presentan una medición del calcio mayor a 2.7 (estos quince individuos tienen una medición cerca a 25 (mmo/L), medida incoherente según las referencias)
Siete pacientes tienen una medición de Fósforo menor de 0.84 (mediciones cercanas a 0.09, siendo estas muy bajas). Por otro lado, seis pacientes presentan mediciones de Fósforo en su cuerpo mayores a 1.5 (cercanas a 8.9, siendo estas muy altas e incoherentes con los rangos degún las referencias).
Para la variable que mide Fosfatasa Alcalina, se encontró una medición menor a 30, igual a 9. Así mismo, se encontraron 34 pacientes com mediciones mayores a 115 y cercanas a 219 (siendo estas muy grandes y violando el límite superior coherente según los estudios de referencia).
Para la variable Sexo, se encontraron que ocho mediciones estaban decodficadas de forma diferetne a 1 y 2. Para la varaible Laboratorio se encontró que 3 pacientes tenían decodificaciones diferentes a los 6 laboratorios asginados originalmente. Finalmente, se encontró que un total de tres pacientes no estaban categorizados correctamente según los rangos de edades datos.
Relizando la identificación de los datos faltantes en R se tiene:
visdat::vis_miss(Calcio) # Una función que visualiza los datos faltantes en la hoja de calculo
## Warning: `gather_()` was deprecated in tidyr 1.2.0.
## Please use `gather()` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
De la salida anterior se evidencia que un total del 0.4% de nuestros datos son faltantes. Las variables que tienen estos datos faltantes son Age, ALP CaMol y PhoMol.
# Una función (Desarrollo propio: Evalua e identifica los datos faltantes por variable e individuo)
miss<-function(Datos,plot=T){
n=nrow(Datos);p=ncol(Datos)
names.obs<-rownames(Datos)
nobs.comp=sum(complete.cases(Datos)) # Cuenta los registros completos
Obs.comp=which(complete.cases(Datos)) # Identifica los registros completos
nobs.miss = sum(!complete.cases(Datos)) # Identifica los registros con datos faltantes.
Obs.miss=which(!complete.cases(Datos)) # Identifica los registros con datos faltantes.
Datos.NA<-is.na(Datos)
Var_Num<- sort(colSums(Datos.NA),decreasing=T)
Var_per<-round(Var_Num/n,3)
Obs_Num<-rowSums(Datos.NA)
names(Obs_Num)<-names.obs
Obs_Num<-sort(Obs_Num,decreasing=T)
Obs_per<-round(Obs_Num/p,3)
lista<-list(n.row = n, n.col = p,n.comp = nobs.comp,Obs.comp = Obs.comp,n.miss = nobs.miss,Obs.miss = Obs.miss, Var.n = Var_Num , Var.p = Var_per, Obs.n= Obs_Num, Obs.per= Obs_per)
if(plot){
#windows(height=10,width=15)
par(mfrow=c(1,2))
coord<-barplot(Var_per,plot=F)
barplot(Var_per,xaxt="n",horiz=T,yaxt="n",xlim=c(-0.2,1), ylim=c(0,max(coord)+1),main= "% datos faltantes por variable")
axis(2,at=coord,labels=names(Var_per), cex.axis=0.5,pos=0,las=2)
axis(1,seq(0,1,0.2),seq(0,1,0.2),pos=0)
coord<-barplot(Obs_per,plot=F)
barplot(Obs_per,xaxt="n",horiz=T,yaxt="n",xlim=c(-0.2,1), ylim=c(0,max(coord)+1),main= "% datos faltantes por registro")
axis(2,at=coord,labels=names(Obs_per),cex.axis=0.5,pos=0,las=2)
axis(1,seq(0,1,0.2),seq(0,1,0.2))
}
return(invisible(lista))
}
Summary.NA = miss(Calcio) # Asignamos el resultado a un objeto lista para consultarlo
Los datos faltantes pertenecen a los registros: 4, 14, 22 ,42, 85 y 105.
Como primera medida, se proceden a solucionar las inconsistencias obvias, esto es, mediante la declaración de niveles correctos para las variables tipo Factor:
level_Sex=c("f"="2","F"="2","M"="1", "m"="1","12"="2","21"="1","22"="2")
level_AgeG=c("85-89"="85 - 89")
Se corrigen los datos faltantes remplazando los encontrados en los registros del enlace:
Calcio[4,2]=73;Calcio[14,2]=76;Calcio[22,4]=64
level_Lab=c("43"="4","21"="2")
Valid_Data=as.data.frame(Valid_Data);Name=colnames(Valid_Data)
OBS=c();for(i in 1:16){OBS[[i]]=which(Valid_Data[,Name[i]]==TRUE)} #Encontrar filas que violan todas las reglas
OBS=unlist(OBS);OBS=OBS[!duplicated(OBS)] #Lista de registros con violación a las reglas
#Se comparan los datos que violan las reglas vs lo datos originales, y se corrigen aquellos erroneos
or=order(OBS);OBS=OBS[or]; Va=Valid_Data[c(OBS),] #Extrae las filas de los registros son incoherencias
Calcio[53,2]=69;Calcio[11,2]=71;Calcio[123,2]=73;Calcio[25,6]=2.53;Calcio[26,6]=2.0;Calcio[27,6]=2.23;Calcio[28,6]=2.43;Calcio[29,6]=2.5;Calcio[30,6]=2.33;Calcio[31,6]=2.4;Calcio[32,6]=2.5;Calcio[33,6]=2.35;Calcio[34,6]=2.25;Calcio[35,6]=2.5;Calcio[36,6]=2.45;Calcio[37,6]=2.33;Calcio[32,7]=3.21;Calcio[176,7]=1.26;Calcio[132,7]=0.84;Calcio[108,7]=0.9;Calcio[60,4]=97
Modificación del formato y transformación de variables
Calcio = transform(Calcio,
Sex=factor(dplyr::recode(Sex,!!!level_Sex)),
AgeG=factor(dplyr::recode(AgeG, !!!level_AgeG),levels=c("65 - 69","70 - 74","75 - 79","80 - 84","85 - 89"),ordered=TRUE),
Lab=factor(dplyr::recode(Lab, !!!level_Lab))
)
summary(Calcio[,c(2,4,6,7)])
Age | ALP | CaMol | PhoMol | |
---|---|---|---|---|
Min. :65.00 | Min. : 42.00 | Min. :1.050 | Min. :0.520 | |
1st Qu.:69.00 | 1st Qu.: 71.25 | 1st Qu.:2.250 | 1st Qu.:0.970 | |
Median :72.00 | Median : 85.00 | Median :2.350 | Median :1.130 | |
Mean :72.32 | Mean : 92.37 | Mean :2.355 | Mean :1.114 | |
3rd Qu.:75.00 | 3rd Qu.:108.75 | 3rd Qu.:2.450 | 3rd Qu.:1.230 | |
Max. :89.00 | Max. :219.00 | Max. :3.200 | Max. :3.210 | |
NA’s :1 | NA | NA’s :1 | NA’s :1 |
En total se corrgieron 3 datos atípicos y 21 por mala digitación.
id.out.uni=function(x,method=c("Standarized","Tukey","Cook")){
id.out=NULL
if(method=="Standarized"){id.out=which(abs(scale(x))>3)}
else if(method=="Tukey"){id.out=which(x%in%(boxplot.stats(x)$out))}
else if(method=="Cook"){model=lm(x~1);CD=cooks.distance(model)
id.out=unname(which(CD>4*mean(CD)))}
return(id.out)
}
par(mfrow=c(1,4))
Boxplot=lapply(Calcio[,-c(1,3,5,8)],boxplot,col="blue")
### Identificar los Datos Atipicos
### Identificar los Datos Atipicos
out_Tukey = lapply(Calcio[,-c(1,3,5,8)],id.out.uni,method="Tukey");out_Tukey=unlist(out_Tukey)
out_Cook = lapply(Calcio[,-c(1,3,5,8)],id.out.uni,method="Cook");out_Cook=unlist(out_Cook)
# Se extraen los valores atípicos que conisiden de acuerdo a los métodos de Tukey y distancias Cook
At1= (out_Cook[order(out_Cook)] %in% out_Tukey[order(out_Tukey)]);At1=out_Cook[which(At1==TRUE)]
Los registros que se pueden considerar atípicos de acuerdo al enfoque univariado y combinando los métodos de Tukey y distancias Cooks son:
At1
## Age1 Age2 Age5 Age6 Age8 ALP5 ALP6 CaMol1 CaMol3 CaMol4
## 9 10 38 59 107 89 94 21 50 147
## PhoMol3
## 78
####### Identificación multivariada de outliers
Calcio.cor = cor(na.omit(Calcio[,-c(1,3,5,8)]),method="pearson")
corrplot::corrplot(Calcio.cor , method = "ellipse",addCoef.col = "black",type="upper")
pairs(Calcio[,-c(1,3,5,8)],lower.panel = panel.smooth, pch = 15)
## Visualización de outliers multivariados
out.mult=function(Datos){
n= nrow(Datos); p= ncol(Datos)
Distance=mahalanobis(Datos,center=colMeans(Datos),cov=cov(Datos))
Limit= qchisq(0.01, lower.tail=FALSE,df=p)
id.dist= which(Distance>Limit)
Score_LOF = DMwR2::lofactor(Datos, k=5)
id.LOF <- order(Score_LOF, decreasing=TRUE)[1:ceiling(0.01*n)]
plot(Distance,pch=20,ylim=c(0,max(Distance)#*1.2))
))
text(id.dist,Distance[id.dist],id.dist, col="red",pos=2,cex=0.8)
abline(h=Limit,col="red",lwd=2,lty=2)
plot(Score_LOF,pch=20,ylim=c(0,max(Score_LOF)#*1.2))
))
text(id.LOF,Score_LOF[id.LOF],id.LOF, col="red",pos=2,cex=0.8)
return(list(Out_dist=id.dist,Out_LOF=id.LOF))
}
id_Out_mult=out.mult(na.omit(Calcio[,-c(1,3,5,8)]))
Y con base en los gráficos anteriores, desde el enfoque multivariado los resgistros a considerarse atípicos son:
## [1] 7 9 21 32 80 94 149
Finalmente, con base en el análisis Univariado y multivariado de datos atípicos se deciden amputar los siguientes tres registros:
Registro | ColNumb | |
---|---|---|
ALP | 7 | 4 |
CaMol | 21 | 6 |
PhoMo | 32 | 7 |
CaMol2 | 146 | 6 |
Finalmente se aíslan los registros que se consideraron anteriormente:
for(i in 1:4){Calcio[Registro[,1][i],Registro[,2][i]]=NA}
Para la amputación de datos faltantes se tiene 3 opciones inicialmente:
Valor medio o Mediana: Consiste en remplazar el NA por la media o mediana de la varaible, dado que este método subestima la varianza y es poco robusto no se tomará en cuenta.
Amputación por regresión: Se predice el NA en función de los facores completos, el problema de este método es que si la asociación de las variables es poca es equivalente a remplazar la media.
Dado que los dos métodos anteriores no convencen, se procederá a realizar la amputación de datos faltantes por el método de las distancias (K vecinos cercanos).
## Imputación por vecindad (KNN)
Datos=Calcio
Datos_ImputKNN<-DMwR2::knnImputation(Datos,k=5,scale=T,meth = "weighAvg")
windows(height=10,width=15); visdat::vis_miss(Datos_ImputKNN)
par(mfrow=c(1,4))
Boxplot=lapply(Calcio[,-c(1,3,5,8)],boxplot,col="blue")
Terminando el proceso de imputación, se obtiene una base de datos limpia (sin datos faltantes) y con algunas correciones de datos atípicos que se consideraron influyentes.
Genere un resumen de los cambios realizados en la hoja de datos. ReporteCambios.txt
Exportando datos limpios de R a excel en formato CSV
## Datos limpios de R a excel
clean_calcium=Datos_ImputKNN
write.csv(clean_calcium,"clean_calcium.csv")
Las diferencias de las variables clínicas: ALP, CaMol, PhoMol entre los grupos de edad (AgeG) , Sexo (Sex) y Laboratorio (Lab).
La estructura de correlación entre las variables cuantitativas: Age, ALP, CaMol, PhoMol.