En primer lugar, realizamos la carga de los ficheros csv obtenidos del articulo con el que vamos a trabajar con los datos obtenidos de los sensores.
# Realizamos la carga del csv.
df_LAlbea <-read.csv("CEIP_LAlbea_ValldAbav2.csv")
# Mostramos la estructura del dataframe cargado.
str(df_LAlbea)
## 'data.frame': 34569 obs. of 14 variables:
## $ published_at: chr "2021-06-01T14:30:06.735Z" "2021-06-01T14:15:06.784Z" "2021-06-01T14:20:06.733Z" "2021-06-01T14:35:06.728Z" ...
## $ date_time : chr "2021-06-01T14:30:06" "2021-06-01T14:15:06" "2021-06-01T14:20:06" "2021-06-01T14:35:06" ...
## $ date : chr "2021-06-01" "2021-06-01" "2021-06-01" "2021-06-01" ...
## $ month : chr "Jun" "Jun" "Jun" "Jun" ...
## $ w_day : chr "Tue" "Tue" "Tue" "Tue" ...
## $ time : chr "14:30:06" "14:15:06" "14:20:06" "14:35:06" ...
## $ hour : int 14 14 14 14 14 14 14 14 14 14 ...
## $ mint : int 30 15 20 35 40 10 55 0 45 50 ...
## $ sec : int 6 6 6 6 6 6 6 6 6 6 ...
## $ temp : num 29.8 29.8 29.8 29.9 30 ...
## $ hum : num 46.3 47.5 47.2 46.1 45.9 ...
## $ co2 : int 390 391 391 393 395 399 400 401 402 402 ...
## $ bat : num 85.2 81.1 82.5 86.5 87.9 ...
## $ sensor_id : chr " CO2_01" " CO2_01" " CO2_01" " CO2_01" ...
# Mostramos una muestra de 10 registros del dataset cargado.
df_LAlbea[sample(nrow(df_LAlbea), 10), ]
Para cargar este csv eliminamos el caracter ” (doble comilla) ya que hemos comprobado previamente el formato del fichero.
# Se define el carácter a eliminar (comillas dobles).
caracter_a_eliminar <- "\\\""
# Leemos el contenido del segundo csv.
csv <- readLines('CEIP_SantMiquel_Vilafames.csv')
# Reemplazamos el carácter con una cadena vacía.
csv_modificado <- gsub(caracter_a_eliminar, "", csv)
# Escribe el contenido modificado de nuevo en el fichero.
writeLines(csv_modificado, 'CEIP_SantMiquel_Vilafames_new.csv')
# Realizamos la carga del csv modificado.
df_SantMiquel <-read.csv("CEIP_SantMiquel_Vilafames_new.csv")
# Mostramos la estructura del dataframe cargado.
str(df_SantMiquel)
## 'data.frame': 38890 obs. of 13 variables:
## $ published_at: chr "2021-05-03T07:00:05.241Z" "2021-05-03T07:05:05.348Z" "2021-05-03T07:20:05.162Z" "2021-05-03T07:25:05.047Z" ...
## $ date_time : chr "2021-05-03T07:00:05" "2021-05-03T07:05:05" "2021-05-03T07:20:05" "2021-05-03T07:25:05" ...
## $ date : chr "5/3/2021" "5/3/2021" "5/3/2021" "5/3/2021" ...
## $ month : chr "May" "May" "May" "May" ...
## $ w_day : chr "Mon" "Mon" "Mon" "Mon" ...
## $ hour : int 7 7 7 7 7 7 7 8 8 8 ...
## $ mint : int 0 5 20 25 35 40 55 0 0 5 ...
## $ sec : int 5 5 5 5 4 4 5 4 5 5 ...
## $ temp : num 26.6 26.5 26.6 26.7 27.2 ...
## $ hum : num 39.4 39.6 40.1 40.3 39.4 ...
## $ co2 : int 302 301 376 520 393 439 385 395 730 386 ...
## $ bat : num 99.5 99.5 99.5 99.5 99.5 ...
## $ sensor_id : chr " CO2_06" " CO2_06" " CO2_06" " CO2_06" ...
# Mostramos una muestra de 10 registros del dataset generado.
df_SantMiquel[sample(nrow(df_SantMiquel), 10), ]
Observamos que ambos dataset son practicamente iguales salvo que el primero contiene una obserbación más, por lo que se decide que trabajar con este primer dataset para emplearlo en el estudio del proyecto.
Con el dataset ya cargado, lo primero que haremos será examinar su estructura y comprobar si cada una de las variables se ajustan al tipo de dato del valor que informan.
Form.Basic = c("striped", "hover", "condensed", "responsive")
kbl(cbind(sapply(df_LAlbea, class)),
caption="Tipo de datos de las variable") %>%
kable_styling(bootstrap_options = Form.Basic) %>%
scroll_box(width = "100%",
height = "400px")
| published_at | character |
| date_time | character |
| date | character |
| month | character |
| w_day | character |
| time | character |
| hour | integer |
| mint | integer |
| sec | integer |
| temp | numeric |
| hum | numeric |
| co2 | integer |
| bat | numeric |
| sensor_id | character |
Eliminamos los espacios en blanco de las variables de tipo character que no son fechas.
# Eliminamos los espacios en blanco de los valores de las variables character.
df_LAlbea$month <- trimws(df_LAlbea$month)
df_LAlbea$w_day <- trimws(df_LAlbea$w_day)
df_LAlbea$sensor_id <- trimws(df_LAlbea$sensor_id)
Las variables sensor_id, month y w_day son variables cualitativas, por lo que modificamos el tipo de datos de estas para corregirlo.
df_LAlbea <- df_LAlbea %>%
dplyr::mutate(sensor_id = as.factor(sensor_id),
month = as.factor(month),
w_day = as.factor(w_day))
Las variables published_at y date_time son de tipo timestamp, por lo que también corregimos el tipo de dato.
# Se convierte date_time a timestamp sin los segundos
df_LAlbea$date_time <- as.POSIXct(strptime(df_LAlbea$date_time, format="%Y-%m-%dT%H:%M:%OS", tz= "UTC"))
df_LAlbea$date_time <- format(df_LAlbea$date_time, "%Y-%m-%d %H:%M")
df_LAlbea$date_time <- as.POSIXct(df_LAlbea$date_time, format="%Y-%m-%d %H:%M", tz= "UTC")
# Se convierte published_at a timestamp
df_LAlbea$published_at <- as.POSIXct(df_LAlbea$published_at, format="%Y-%m-%dT%H:%M:%OS", tz="UTC")
Las variables date y time son de tipo fecha, por lo que también corregimos el tipo de dato.
# Se convierten a tipo date
df_LAlbea$date <- as.Date(df_LAlbea$date, format="%Y-%m-%d")
df_LAlbea$time <- strptime(df_LAlbea$time, format="%H:%M:%S")
Vamos a comprobar si existen registros duplicados. En caso afirmativo, eliminaremos dichos registros del dataset.
# Obtenemos los registros duplicados que pudieran existir en el dataset.
duplicados_LAlbea <- duplicated(df_LAlbea)
# Mostramos los registros duplicados.
df_LAlbea[duplicados_LAlbea, ]
# Contamos los registros duplicados.
sum(duplicados_LAlbea)
## [1] 169
# Eliminamos los registros duplicados.
df_LAlbea <- df_LAlbea[!duplicados_LAlbea, ]
Comprobamos si existen valores nulos en el dataset.
# Comprobamos la existencia de valores nulos.
sapply(df_LAlbea, function(x) sum(is.na(x)))
## published_at date_time date month w_day time
## 0 0 0 0 0 0
## hour mint sec temp hum co2
## 0 0 0 0 0 0
## bat sensor_id
## 0 0
Comprobamos si existen valores extremos en el dataset.
# Generamos los valores mínimos y máximos de las variables cuantitativas.
num_vars_LAlbea <- c('hour', 'mint', 'sec', 'temp', 'hum', 'co2','bat')
min_var <- as.vector(sapply(df_LAlbea[, num_vars_LAlbea], min,na.rm = TRUE))
max_var <- as.vector(sapply(df_LAlbea[, num_vars_LAlbea], max,na.rm = TRUE))
# Convertimos los valores a formato no exponencial
min_var.formatted <- format(min_var, scientific = FALSE)
max_var.formatted <- format(max_var, scientific = FALSE)
# Mostramos la tabla con los resultados
kable(data.frame(Variables= names(df_LAlbea[, num_vars_LAlbea]),
min = min_var.formatted,
max = max_var.formatted),
digits=2, caption="Mínimo y máximo") %>%
kable_styling(bootstrap_options = Form.Basic)
| Variables | min | max |
|---|---|---|
| hour | 0.00 | 23.00 |
| mint | 0.00 | 55.00 |
| sec | 3.00 | 38.00 |
| temp | -13600000000000000.00 | 44.46 |
| hum | 21.04 | 64.18 |
| co2 | 358.00 | 1989.00 |
| bat | 0.00 | 100.00 |
Se observa que existe algún valor o valores erróneos en la variable temp, decidimos sustituir dichos valores por la media de la temperatura por sensor_id, date y hour, ya que el resto de valores de las variables restantes son correctos. A continuación, comprobamos si se soluciona dicho problema.
# Mostramos todos los registros por debajo del valor 0.
print(df_LAlbea[df_LAlbea$temp < 0, ])
## published_at date_time date month w_day
## 15972 2021-06-14 04:50:05 2021-06-14 04:50:00 2021-06-14 Jun Mon
## time hour mint sec temp hum co2 bat sensor_id
## 15972 2024-05-21 04:50:05 4 50 5 -1.36e+16 34.17 445 98.83 CO2_03
# Sustituimos el valor de la temperatura por la media
df_LAlbea <- df_LAlbea %>%
group_by(sensor_id, date, hour) %>%
mutate(
temp_media = mean(temp[temp > 0]),
temp = ifelse(temp < 0, temp_media, temp)
) %>%
ungroup() %>%
select(-temp_media)
# Mostramos estadísticas de la variable temp.
summary(df_LAlbea$temp)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 22.60 28.72 30.00 29.96 31.29 44.46
# Crear un gráfico para cada variable
fig <- plot_ly(y = df_LAlbea$hour, type = "box", name="Hora")
fig <- fig %>% add_trace(y = df_LAlbea$mint, name="Minuto")
fig <- fig %>% add_trace(y = df_LAlbea$sec, name="Segundo")
fig <- fig %>% add_trace(y = df_LAlbea$temp, name="Temperatura")
fig <- fig %>% add_trace(y = df_LAlbea$hum, name="Humedad")
fig <- fig %>% add_trace(y = df_LAlbea$bat, name="Batería")
fig <- fig %>% layout(title = "Distribución de las variables", yaxis = list(title = 'Valores'))
fig
fig <- plot_ly(y = df_LAlbea$co2, type = "box", name="CO2")
fig <- fig %>% layout(title = "Distribución de las variables", yaxis = list(title = 'Valores'))
fig
En este nuevo análisis se obserba que todas las variables contienen valores dentro de un rango aceptable y no existen valores outlier.
Ahora examinaremos las variables del dataset mostrando los datos resumen de cada una de ellas.
options(knitr.kable.NA = '')
kable(summary(df_LAlbea),
digits=2,
align='l',
caption="Datos resumen") %>%
kable_styling(bootstrap_options = Form.Basic) %>%
scroll_box(width = "100%", height = "470px")
| published_at | date_time | date | month | w_day | time | hour | mint | sec | temp | hum | co2 | bat | sensor_id | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Min. :2021-06-01 00:00:05.73 | Min. :2021-06-01 00:00:00.00 | Min. :2021-06-01 | Jun:34400 | Fri:4560 | Min. :2024-05-21 00:00:04.00 | Min. : 0.00 | Min. : 0.00 | Min. : 3.0 | Min. :22.60 | Min. :21.04 | Min. : 358.0 | Min. : 0.00 | CO2_01:6164 | |
| 1st Qu.:2021-06-06 17:23:50.61 | 1st Qu.:2021-06-06 17:23:45.00 | 1st Qu.:2021-06-06 | Mon:4636 | 1st Qu.:2024-05-21 05:55:06.00 | 1st Qu.: 5.00 | 1st Qu.:10.00 | 1st Qu.: 5.0 | 1st Qu.:28.72 | 1st Qu.:40.82 | 1st Qu.: 415.0 | 1st Qu.: 95.57 | CO2_02:6101 | ||
| Median :2021-06-12 00:55:05.50 | Median :2021-06-12 00:55:00.00 | Median :2021-06-12 | Sat:4634 | Median :2024-05-21 12:10:05.00 | Median :12.00 | Median :30.00 | Median : 5.0 | Median :30.00 | Median :45.30 | Median : 429.0 | Median : 96.15 | CO2_03:6218 | ||
| Mean :2021-06-12 03:58:31.88 | Mean :2021-06-12 03:58:26.08 | Mean :2021-06-11 | Sun:4631 | Mean :2024-05-21 12:00:22.73 | Mean :11.55 | Mean :27.52 | Mean : 5.3 | Mean :29.96 | Mean :44.57 | Mean : 464.9 | Mean : 94.45 | CO2_04:4216 | ||
| 3rd Qu.:2021-06-17 11:05:06.52 | 3rd Qu.:2021-06-17 11:05:00.00 | 3rd Qu.:2021-06-17 | Thu:4622 | 3rd Qu.:2024-05-21 18:00:06.00 | 3rd Qu.:18.00 | 3rd Qu.:45.00 | 3rd Qu.: 6.0 | 3rd Qu.:31.29 | 3rd Qu.:49.35 | 3rd Qu.: 448.0 | 3rd Qu.: 98.07 | CO2_05:5582 | ||
| Max. :2021-06-23 23:55:05.89 | Max. :2021-06-23 23:55:00.00 | Max. :2021-06-23 | Tue:5624 | Max. :2024-05-21 23:55:08.00 | Max. :23.00 | Max. :55.00 | Max. :38.0 | Max. :44.46 | Max. :64.18 | Max. :1989.0 | Max. :100.00 | CO2_06:6119 | ||
| Wed:5693 |
Dado que el estudio que vamos a realizar se basa en una serie temporal, vamos a comprobar que no tenemos ningún gap en los datos. En primer lugar, vamos a comprobar que el rango de fechas en la toma de datos de los sensores es la misma para todos.
# Agrupamos los datos por 'sensor_id' y luego resumimos para obtener las fechas mínima y máxima
tabla_fechas <- df_LAlbea %>%
group_by(sensor_id) %>%
summarise(
fecha_minima = min(date_time),
fecha_maxima = max(date_time)
)
# Mostramos La tabla resumen
print(tabla_fechas)
## # A tibble: 6 × 3
## sensor_id fecha_minima fecha_maxima
## <fct> <dttm> <dttm>
## 1 CO2_01 2021-06-01 00:00:00 2021-06-23 23:55:00
## 2 CO2_02 2021-06-01 00:00:00 2021-06-23 23:55:00
## 3 CO2_03 2021-06-01 00:00:00 2021-06-23 23:55:00
## 4 CO2_04 2021-06-01 00:00:00 2021-06-22 23:30:00
## 5 CO2_05 2021-06-01 00:05:00 2021-06-22 05:05:00
## 6 CO2_06 2021-06-01 00:00:00 2021-06-23 23:55:00
# Calculamos el número de registros por sensor por día
conteo_sensores <- df_LAlbea %>%
group_by(sensor_id, date) %>%
summarise(conteo = n())
# Crea un gráfico para cada sensor
ggplot(conteo_sensores, aes(x=date, y=conteo)) +
geom_line() +
facet_wrap(~sensor_id, scales = "free_y") +
theme_minimal() +
labs(x="Fecha", y="Número de Registros", title="Distribución del Número de Registros por Sensor por Día") +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
Podemos comprobar como el sensor CO2_04 no registra datos a partir del día 15-06-2021 y como el sensor CO2_05 carece de datos para los días 22 y 23-06-2021. Dado que el resto de sensores mantiene una distribución correcta de los datos, se decide prescindir de los datos de estos dos sensores para utilizarlos en el proyecto, ya que el alto número de registros faltantes puede desvirtuar el resultado final.
Ahora generaremos la secuencia completa cada 5 minutos para la frecuencia determinada y comporbaremos si tenemos algún gap para cada uno de los sensores.
# Eliminamos los registros de los sensores 'CO2_04' y 'CO2_05'
df_LAlbea_filtrado <- df_LAlbea %>%
filter(sensor_id != 'CO2_04' & sensor_id != 'CO2_05')
# Generamos una secuencia completa de fechas y horas según el rango definido
fecha_min <- min(df_LAlbea_filtrado$date_time)
fecha_max <- max(df_LAlbea_filtrado$date_time)
secuencia_completa <- seq(fecha_min, fecha_max, by = "5 mins")
# Generamos un dataset con la secuencia completa para cada uno de los sensores
df_sec_completo <- expand.grid(sensor_id = unique(df_LAlbea_filtrado$sensor_id), date_time = secuencia_completa)
df_LAlbea_completo <- left_join(df_sec_completo, df_LAlbea_filtrado, by = c("sensor_id", "date_time"))
# Identificamos los registros faltantes
registros_faltantes <- df_LAlbea_completo %>% filter(is.na(df_LAlbea_completo$co2))
# Contamos los registros faltantes por sensor_id
conteo_faltantes <- registros_faltantes %>% group_by(sensor_id) %>% summarise(registros_faltantes = n())
# Mostramos los resultados
print(conteo_faltantes)
## # A tibble: 4 × 2
## sensor_id registros_faltantes
## <fct> <int>
## 1 CO2_01 460
## 2 CO2_02 523
## 3 CO2_03 407
## 4 CO2_06 505
Dado que los valores que nos interesan calcular son los de temperatura, humedad, co2 y bateria, eliminamos el resto de variables innecesarias para este proceso de rellenar los valores faltantes.
# Eliminamos las columnas que no participan en este proceso
df_LAlbea_completo <- subset(df_LAlbea_completo, select = -c(published_at, month, w_day, time, hour, mint, sec))
# Rellenamos los valores faltantes del campo date apoyandonos en date_time
df_LAlbea_completo$date[is.na(df_LAlbea_completo$date)] <- as.Date(df_LAlbea_completo$date_time[is.na(df_LAlbea_completo$date)], tz= "UTC")
# Calculamos las medias por sensor_id y fecha
medias <- df_LAlbea_completo %>%
group_by(sensor_id, date) %>%
summarise(
temp_media = mean(temp, na.rm = TRUE),
hum_media = mean(hum, na.rm = TRUE),
co2_media = mean(co2, na.rm = TRUE),
bat_media = mean(bat, na.rm = TRUE)
)
# Ajustamos el formato de los datos obtenidos de las medias a nuestro dataset
medias$temp_media <- round(medias$temp_media, digits = 2)
medias$hum_media <- round(medias$hum_media, digits = 2)
medias$co2_media <- as.integer(medias$co2_media)
medias$bat_media <- round(medias$bat_media, digits = 2)
# Unimos las medias al dataset original
df_LAlbea_completo <- df_LAlbea_completo %>%
left_join(medias, by = c("sensor_id", "date"))
# Reemplazamos los valores NA con las medias correspondientes
df_LAlbea_completo$temp[is.na(df_LAlbea_completo$temp)] <- df_LAlbea_completo$temp_media[is.na(df_LAlbea_completo$temp)]
df_LAlbea_completo$hum[is.na(df_LAlbea_completo$hum)] <- df_LAlbea_completo$hum_media[is.na(df_LAlbea_completo$hum)]
df_LAlbea_completo$co2[is.na(df_LAlbea_completo$co2)] <- df_LAlbea_completo$co2_media[is.na(df_LAlbea_completo$co2)]
df_LAlbea_completo$bat[is.na(df_LAlbea_completo$bat)] <- df_LAlbea_completo$bat_media[is.na(df_LAlbea_completo$bat)]
# Eliminamos las columnas de medias adicionales y fecha empleadas en el proceso
df_LAlbea_completo <- subset(df_LAlbea_completo, select = -c(date, temp_media, hum_media, co2_media, bat_media))
# Mostramos los resultados
df_LAlbea[sample(nrow(df_LAlbea_completo), 10), ]
A continuación, cargamos los datos de las caracteristicas de las aulas donde se instalaron los sensores.
# Realizamos la carga del csv con las caracteristicas de las aulas.
df_classroom_LAlbea <-read.csv("Classroom_ValldAbav2.csv")
# Mostramos una muestra de 10 registros del dataset.
df_classroom_LAlbea[sample(nrow(df_classroom_LAlbea)), ]
Examinamos el tipo de dato de cada una de las variables para comprobar si se ajustan al tipo de datos de cada una de ellas.
Form.Basic = c("striped", "hover", "condensed", "responsive")
kbl(cbind(sapply(df_classroom_LAlbea, class)),
caption="Tipo de datos de las variable") %>%
kable_styling(bootstrap_options = Form.Basic) %>%
scroll_box(width = "100%",
height = "400px")
| sensor_id | character |
| grade | character |
| number_students | integer |
| metres | numeric |
| windows | integer |
| doors | integer |
| orientation | character |
Eliminamos los espacios en blanco de las variables de tipo character que no son fechas.
# Eliminamos los espacios en blanco de los valores de las variables character.
df_classroom_LAlbea$sensor_id <- trimws(df_classroom_LAlbea$sensor_id)
df_classroom_LAlbea$grade <- trimws(df_classroom_LAlbea$grade)
df_classroom_LAlbea$orientation <- trimws(df_classroom_LAlbea$orientation)
A pesar de que tenemos variables numericas en este dataset, todas las variables que este recoge son categóricas, puesto que nos estan indicando una característica del aula donde ha sido instalado el sensor de la toma de datos, por lo que modificamos el tipo de datos de estas para corregirlo.
df_classroom_LAlbea <- df_classroom_LAlbea %>%
dplyr::mutate(sensor_id = as.factor(sensor_id),
grade = as.factor(grade),
number_students = as.factor(number_students),
metres = as.factor(metres),
windows = as.factor(windows),
doors = as.factor(doors),
orientation = as.factor(orientation))
Ahora examinaremos las variables del dataset mostrando los datos resumen de cada una de ellas.
options(knitr.kable.NA = '')
kable(summary(df_classroom_LAlbea),
digits=2,
align='l',
caption="Datos resumen Classroom ") %>%
kable_styling(bootstrap_options = Form.Basic) %>%
scroll_box(width = "100%", height = "470px")
| sensor_id | grade | number_students | metres | windows | doors | orientation | |
|---|---|---|---|---|---|---|---|
| S_01:1 | 1st:1 | 14:1 | 35.8 :1 | 2:3 | 1:6 | Playground:6 | |
| S_02:1 | 2nd:1 | 16:2 | 40.9 :1 | 3:1 | |||
| S_03:1 | 3rd:1 | 20:1 | 44.56:1 | 4:2 | |||
| S_04:1 | 4th:1 | 24:1 | 65.3 :1 | ||||
| S_05:1 | 5th:1 | 25:1 | 67 :1 | ||||
| S_06:1 | 6th:1 | 69.17:1 |
Como vamos a unir ambos datasets por la variable sensor_id, comprobamos que el valor de ambos sea igual.
# Revisamos la variable
table(df_LAlbea_completo$sensor_id)
##
## CO2_01 CO2_02 CO2_03 CO2_04 CO2_05 CO2_06
## 6624 6624 6624 0 0 6624
table(df_classroom_LAlbea$sensor_id)
##
## S_01 S_02 S_03 S_04 S_05 S_06
## 1 1 1 1 1 1
Dado que el valor de los sensores no es el mismo en ambos datasets, modificamos el valor del campo sensor_id con la nomencaltura del contenido del dataset df_LAlbea.
# Reemplazamos los valores en el dataset df_classroom_LAlbea
df_classroom_LAlbea$sensor_id <- gsub("S_01", "CO2_01", df_classroom_LAlbea$sensor_id)
df_classroom_LAlbea$sensor_id <- gsub("S_02", "CO2_02", df_classroom_LAlbea$sensor_id)
df_classroom_LAlbea$sensor_id <- gsub("S_03", "CO2_03", df_classroom_LAlbea$sensor_id)
df_classroom_LAlbea$sensor_id <- gsub("S_04", "CO2_04", df_classroom_LAlbea$sensor_id)
df_classroom_LAlbea$sensor_id <- gsub("S_05", "CO2_05", df_classroom_LAlbea$sensor_id)
df_classroom_LAlbea$sensor_id <- gsub("S_06", "CO2_06", df_classroom_LAlbea$sensor_id)
En este punto realizamos la unión del dataset que contiene los valores obtenidos de los sensores con el dataset con las caracteristicas de las aulas donde se utilizó cada uno de los sensores, empleando como clave común la variable sensor_id.
# Realizamos la unión de ambos datasets utilizando la variable sensor_id como clave común.
dfu_LAlbea <- merge(df_LAlbea_completo, df_classroom_LAlbea, by = "sensor_id")
# Mostramos el detalle del dataframe generado.
dfu_LAlbea[sample(nrow(dfu_LAlbea), 10), ]
Por último, exportamos el dataset final generado a un fichero csv.
# Exportamos nuestro dataset unificado a CSV.
write.csv(dfu_LAlbea, file = "CEIP_LAlbea_co2.csv", row.names = FALSE)