library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.4.4 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.0
## ✔ 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(ggplot2)
library(lubridate)
library(corrplot)
## corrplot 0.92 loaded
library(caret)
## Loading required package: lattice
##
## Attaching package: 'caret'
##
## The following object is masked from 'package:purrr':
##
## lift
library(nnet)
library(naivebayes)
## naivebayes 1.0.0 loaded
## For more information please visit:
## https://majkamichal.github.io/naivebayes/
library(lindia)
sensor_data <- read.csv("/Users/manojdannana/Desktop/Manoj/IUPUI/class work/intro to statistics/datasets/Occupancy_Estimation.csv")
This is a dataset with various sensor readings over time. Each row represents a snapshot of the sensor data at a specific time. Here’s a breakdown of the columns:
Date
Time
S1_Temp: Temperature reading from sensor 1
S2_Temp: Temperature reading from sensor 2
S3_Temp: Temperature reading from sensor 3
S4_Temp: Temperature reading from sensor 4
S1_Light: Light intensity reading from sensor 1
S2_Light: Light intensity reading from sensor 2
S3_Light: Light intensity reading from sensor 3
S4_Light: Light intensity reading from sensor 4
S1_Sound: Sound level reading from sensor 1
S2_Sound: Sound level reading from sensor 2
S3_Sound: Sound level reading from sensor 3
S4_Sound: Sound level reading from sensor 4
S5_CO2: CO2 level reading from sensor 5
S5_CO2_Slope: Slope of CO2 level change over time from sensor 5
S6_PIR: Passive Infrared (PIR) motion detection reading from sensor 6
S7_PIR: Passive Infrared (PIR) motion detection reading from sensor 7
Room_Occupancy_Count: Count of occupants in the room
Let’s do some eda on this dataset
Description: In modern smart buildings, optimizing room occupancy is crucial for efficient resource utilization, energy conservation, and enhanced occupant comfort. By leveraging sensor data from temperature, light intensity, sound level, CO2 level, and other environmental factors, we can develop a predictive room occupancy management system.
Key Features:
Real-time Monitoring: Continuously monitor environmental conditions and occupancy status in various rooms of the building.
Predictive Analytics: Utilize historical sensor data to predict future room occupancy counts based on current environmental factors.
Occupancy Forecasting: Forecast occupancy trends for different time intervals (e.g., hourly, daily) to anticipate peak usage periods and plan resource allocation accordingly.
Energy Optimization: Adjust heating, ventilation, and air conditioning (HVAC) systems based on predicted occupancy patterns to optimize energy consumption while maintaining occupant comfort.
Space Utilization: Analyze room occupancy patterns to identify underutilized spaces and optimize space allocation for various activities or functions.
Alerts and Notifications: Send alerts and notifications to building managers or occupants when occupancy levels deviate from expected patterns or reach predefined thresholds.
Application Workflow:
Data Collection: Gather sensor data from temperature sensors, light sensors, sound sensors, CO2 sensors, and occupancy sensors installed in different rooms of the building.
Data Preprocessing: Clean and preprocess the sensor data, including handling missing values, removing outliers, and engineering relevant features such as time-based features.
Model Training: Train machine learning models, such as regression or classification models, using historical sensor data to predict room occupancy counts based on environmental factors.
Real-time Prediction: Deploy the trained models in a real-time predictive analytics system that continuously ingests new sensor data and generates predictions for room occupancy counts.
Visualization and Insights: Visualize the predicted occupancy counts and environmental factors in an intuitive dashboard, providing building managers with actionable insights for optimizing room occupancy and energy usage.
Benefits:
Energy Efficiency: By adjusting HVAC systems based on predicted occupancy patterns, the system can significantly reduce energy consumption and operational costs.
Improved Comfort: Occupants benefit from optimized room conditions tailored to their occupancy needs, leading to enhanced comfort and satisfaction.
Space Optimization: Building managers can identify opportunities to optimize space allocation and utilization based on occupancy trends, leading to better space management and resource allocation.
Proactive Maintenance: Early detection of anomalies or deviations in sensor data can facilitate proactive maintenance and address potential issues before they escalate.
Let’s analyse the data to see if we can extract some important information or patterns.
I would like to first see how the room_occupancy is affected by
entities such as light, temperature and sound.
Let’s analyze these elements first
Let’s first create columns for means of sensors used to capture same data
sensor_data$Mean_Temp <- rowMeans(sensor_data[, c("S1_Temp", "S2_Temp", "S3_Temp", "S4_Temp")])
sensor_data$Mean_Light <- rowMeans(sensor_data[, c("S1_Light", "S2_Light", "S3_Light", "S4_Light")])
sensor_data$Mean_Sound <- rowMeans(sensor_data[, c("S1_Sound", "S2_Sound", "S3_Sound", "S4_Sound")])
The Room_Occupancy_Count may seem like a numerical column but it has only four distinct values.
unique(sensor_data$Room_Occupancy_Count)
## [1] 1 2 3 0
It would be better to consider Room_Occupancy_Count as a Categorical column rather than a numerical column. Type Converting the column into a factor would be better for our analysis.
sensor_data$Room_Occupancy_Count <- factor(sensor_data$Room_Occupancy_Count)
summary(sensor_data)
## Date Time S1_Temp S2_Temp
## Length:10129 Length:10129 Min. :24.94 Min. :24.75
## Class :character Class :character 1st Qu.:25.19 1st Qu.:25.19
## Mode :character Mode :character Median :25.38 Median :25.38
## Mean :25.45 Mean :25.55
## 3rd Qu.:25.63 3rd Qu.:25.63
## Max. :26.38 Max. :29.00
## S3_Temp S4_Temp S1_Light S2_Light
## Min. :24.44 Min. :24.94 Min. : 0.00 Min. : 0.00
## 1st Qu.:24.69 1st Qu.:25.44 1st Qu.: 0.00 1st Qu.: 0.00
## Median :24.94 Median :25.75 Median : 0.00 Median : 0.00
## Mean :25.06 Mean :25.75 Mean : 25.45 Mean : 26.02
## 3rd Qu.:25.38 3rd Qu.:26.00 3rd Qu.: 12.00 3rd Qu.: 14.00
## Max. :26.19 Max. :26.56 Max. :165.00 Max. :258.00
## S3_Light S4_Light S1_Sound S2_Sound
## Min. : 0.00 Min. : 0.00 Min. :0.0600 Min. :0.0400
## 1st Qu.: 0.00 1st Qu.: 0.00 1st Qu.:0.0700 1st Qu.:0.0500
## Median : 0.00 Median : 0.00 Median :0.0800 Median :0.0500
## Mean : 34.25 Mean :13.22 Mean :0.1682 Mean :0.1201
## 3rd Qu.: 50.00 3rd Qu.:22.00 3rd Qu.:0.0800 3rd Qu.:0.0600
## Max. :280.00 Max. :74.00 Max. :3.8800 Max. :3.4400
## S3_Sound S4_Sound S5_CO2 S5_CO2_Slope
## Min. :0.0400 Min. :0.0500 Min. : 345.0 Min. :-6.29615
## 1st Qu.:0.0600 1st Qu.:0.0600 1st Qu.: 355.0 1st Qu.:-0.04615
## Median :0.0600 Median :0.0800 Median : 360.0 Median : 0.00000
## Mean :0.1581 Mean :0.1038 Mean : 460.9 Mean :-0.00483
## 3rd Qu.:0.0700 3rd Qu.:0.1000 3rd Qu.: 465.0 3rd Qu.: 0.00000
## Max. :3.6700 Max. :3.4000 Max. :1270.0 Max. : 8.98077
## S6_PIR S7_PIR Room_Occupancy_Count Mean_Temp
## Min. :0.00000 Min. :0.00000 0:8228 Min. :24.89
## 1st Qu.:0.00000 1st Qu.:0.00000 1: 459 1st Qu.:25.13
## Median :0.00000 Median :0.00000 2: 748 Median :25.36
## Mean :0.09014 Mean :0.07957 3: 694 Mean :25.45
## 3rd Qu.:0.00000 3rd Qu.:0.00000 3rd Qu.:25.61
## Max. :1.00000 Max. :1.00000 Max. :26.80
## Mean_Light Mean_Sound
## Min. : 0.00 Min. :0.0525
## 1st Qu.: 0.00 1st Qu.:0.0625
## Median : 0.00 Median :0.0675
## Mean : 24.73 Mean :0.1376
## 3rd Qu.: 30.50 3rd Qu.:0.0750
## Max. :193.75 Max. :3.1825
Let’s plot box plots to check for outliers
ggplot(sensor_data, aes(x = "", y = Mean_Temp)) +
geom_boxplot() +
labs(x = "", y = "Mean Temperature") +
theme_minimal() +
ggtitle("Box Plot of Mean Temperature") +
theme(plot.title = element_text(hjust = 0.5)) +
coord_flip()
ggplot(sensor_data, aes(x = "", y = Mean_Light)) +
geom_boxplot() +
labs(x = "", y = "Mean Light") +
theme_minimal() +
ggtitle("Box Plot of Mean Light") +
theme(plot.title = element_text(hjust = 0.5)) +
coord_flip()
ggplot(sensor_data, aes(x = "", y = Mean_Sound)) +
geom_boxplot() +
labs(x = "", y = "Mean Sound") +
theme_minimal() +
ggtitle("Box Plot of Mean Sound") +
theme(plot.title = element_text(hjust = 0.5)) +
coord_flip()
There seem to be few ouliers in Temperature but the range of those outliers is very small (~ 0.5 degrees) and hence can be ignored as it’s very common for temperature to fluctuate that much during the day.
There seem to be a lot of outliers in Light readings, this could be because of a lot of data being captured during night time where lights are generally switched off. Hence these ouliers can be explained.
In case of sound readings there seem to be an unusual amount of outliers. I feel these are due to some unrecorded factors like a nearby construction or the geographical location of the room for which the observations are taken. Hence I feel that this is weird but this cannot be explained with the data we have.
We are doing this in order to see which columns are relevant and which are not
colnames(sensor_data)
## [1] "Date" "Time" "S1_Temp"
## [4] "S2_Temp" "S3_Temp" "S4_Temp"
## [7] "S1_Light" "S2_Light" "S3_Light"
## [10] "S4_Light" "S1_Sound" "S2_Sound"
## [13] "S3_Sound" "S4_Sound" "S5_CO2"
## [16] "S5_CO2_Slope" "S6_PIR" "S7_PIR"
## [19] "Room_Occupancy_Count" "Mean_Temp" "Mean_Light"
## [22] "Mean_Sound"
selected_data <- sensor_data[, c("S5_CO2", "S5_CO2_Slope", "S6_PIR", "S7_PIR", "Room_Occupancy_Count", "Mean_Temp", "Mean_Light", "Mean_Sound")]
selected_data <- sapply(selected_data, as.numeric)
selected_data <- na.omit(selected_data)
correlation_matrix <- cor(selected_data)
print(correlation_matrix)
## S5_CO2 S5_CO2_Slope S6_PIR S7_PIR
## S5_CO2 1.00000000 0.06922002 0.3952649 0.4734369
## S5_CO2_Slope 0.06922002 1.00000000 0.3683741 0.4253462
## S6_PIR 0.39526487 0.36837408 1.0000000 0.5711250
## S7_PIR 0.47343692 0.42534621 0.5711250 1.0000000
## Room_Occupancy_Count 0.66014450 0.60110500 0.6331329 0.6951375
## Mean_Temp 0.82872506 0.15395670 0.4549522 0.4742304
## Mean_Light 0.62662861 0.50745387 0.5932932 0.5897310
## Mean_Sound 0.47243812 0.40177114 0.5631196 0.6042985
## Room_Occupancy_Count Mean_Temp Mean_Light Mean_Sound
## S5_CO2 0.6601445 0.8287251 0.6266286 0.4724381
## S5_CO2_Slope 0.6011050 0.1539567 0.5074539 0.4017711
## S6_PIR 0.6331329 0.4549522 0.5932932 0.5631196
## S7_PIR 0.6951375 0.4742304 0.5897310 0.6042985
## Room_Occupancy_Count 1.0000000 0.6926980 0.8541599 0.6535266
## Mean_Temp 0.6926980 1.0000000 0.6858731 0.5063958
## Mean_Light 0.8541599 0.6858731 1.0000000 0.6468916
## Mean_Sound 0.6535266 0.5063958 0.6468916 1.0000000
corrplot(correlation_matrix, method = "color", type = "upper", order = "hclust", addrect = 8, tl.cex = 0.7, addCoef.col = "black")
From the above correlation heatmap it’s clearly evident that Room_Occupancy_Count is having very strong positive correlation with all the other columns.
Let’s analyse for each of temperature sensors, light sensors and sound sensors
selected_data <- sensor_data[, c("S1_Temp", "S2_Temp", "S3_Temp", "S4_Temp", "Room_Occupancy_Count")]
selected_data <- sapply(selected_data, as.numeric)
selected_data <- na.omit(selected_data)
correlation_matrix <- cor(selected_data)
print(correlation_matrix)
## S1_Temp S2_Temp S3_Temp S4_Temp
## S1_Temp 1.0000000 0.7997073 0.9488387 0.8552793
## S2_Temp 0.7997073 1.0000000 0.7655254 0.6965813
## S3_Temp 0.9488387 0.7655254 1.0000000 0.8851858
## S4_Temp 0.8552793 0.6965813 0.8851858 1.0000000
## Room_Occupancy_Count 0.7008682 0.6712635 0.6520466 0.5265087
## Room_Occupancy_Count
## S1_Temp 0.7008682
## S2_Temp 0.6712635
## S3_Temp 0.6520466
## S4_Temp 0.5265087
## Room_Occupancy_Count 1.0000000
corrplot(correlation_matrix, method = "color", type = "upper", order = "hclust", addrect = 8, tl.cex = 0.7, addCoef.col = "black", col = colorRampPalette(c("blue", "white", "red"))(10))
From the above correlation heatmap it’s clearly evident that Room_Occupancy_Count is having very strong positive correlation with all the temperature sensors. But the temperature sensor S2_Temp has a smaller correlation that the other sensors. This should be noted for further analysis.
selected_data <- sensor_data[, c("S1_Light", "S2_Light", "S3_Light", "S4_Light", "Room_Occupancy_Count")]
selected_data <- sapply(selected_data, as.numeric)
selected_data <- na.omit(selected_data)
correlation_matrix <- cor(selected_data)
print(correlation_matrix)
## S1_Light S2_Light S3_Light S4_Light
## S1_Light 1.0000000 0.8420901 0.8164378 0.5108527
## S2_Light 0.8420901 1.0000000 0.7095795 0.4589136
## S3_Light 0.8164378 0.7095795 1.0000000 0.5794836
## S4_Light 0.5108527 0.4589136 0.5794836 1.0000000
## Room_Occupancy_Count 0.8490581 0.7887639 0.7930813 0.3557147
## Room_Occupancy_Count
## S1_Light 0.8490581
## S2_Light 0.7887639
## S3_Light 0.7930813
## S4_Light 0.3557147
## Room_Occupancy_Count 1.0000000
corrplot(correlation_matrix, method = "color", type = "upper", order = "hclust", addrect = 8, tl.cex = 0.7, addCoef.col = "black", col = colorRampPalette(c("white", "grey", "pink"))(10))
From the above correlation heatmap it’s clearly evident that Room_Occupancy_Count is having very strong positive correlation with all the light sensors except S4_Light. This should be noted for further analysis.
selected_data <- sensor_data[, c("S1_Sound", "S2_Sound", "S3_Sound", "S4_Sound", "Room_Occupancy_Count")]
selected_data <- sapply(selected_data, as.numeric)
selected_data <- na.omit(selected_data)
correlation_matrix <- cor(selected_data)
print(correlation_matrix)
## S1_Sound S2_Sound S3_Sound S4_Sound
## S1_Sound 1.0000000 0.5600616 0.5407361 0.5577329
## S2_Sound 0.5600616 1.0000000 0.5298299 0.5786347
## S3_Sound 0.5407361 0.5298299 1.0000000 0.6966697
## S4_Sound 0.5577329 0.5786347 0.6966697 1.0000000
## Room_Occupancy_Count 0.5737483 0.5578530 0.5316849 0.4602873
## Room_Occupancy_Count
## S1_Sound 0.5737483
## S2_Sound 0.5578530
## S3_Sound 0.5316849
## S4_Sound 0.4602873
## Room_Occupancy_Count 1.0000000
corrplot(correlation_matrix, method = "color", type = "upper", order = "hclust", addrect = 8, tl.cex = 0.7, addCoef.col = "black", col = colorRampPalette(c("darkblue", "blue", "skyblue"))(10))
From the above correlation heatmap it’s clearly evident that Room_Occupancy_Count is having very strong positive correlation with all the sound sensors except S1_Sound. This should be noted for further analysis.
correlation_with_target <- cor(sensor_data[, c( "S1_Temp", "S2_Temp", "S3_Temp", "S4_Temp", "S1_Light", "S2_Light", "S3_Light", "S4_Light", "S1_Sound", "S2_Sound", "S3_Sound", "S4_Sound", "S5_CO2", "S5_CO2_Slope", "S6_PIR", "S7_PIR")], as.numeric(sensor_data$Room_Occupancy_Count))
abs(correlation_with_target) > 0.5
## [,1]
## S1_Temp TRUE
## S2_Temp TRUE
## S3_Temp TRUE
## S4_Temp TRUE
## S1_Light TRUE
## S2_Light TRUE
## S3_Light TRUE
## S4_Light FALSE
## S1_Sound TRUE
## S2_Sound TRUE
## S3_Sound TRUE
## S4_Sound FALSE
## S5_CO2 TRUE
## S5_CO2_Slope TRUE
## S6_PIR TRUE
## S7_PIR TRUE
From the above correlation analysis we can see that two columns aren’t good for our model, hence we select the all the others.
Features Selected for Model: S1_Temp, S2_Temp, S3_Temp, S4_Temp, S1_Light, S2_Light, S3_Light, S1_Sound, S2_Sound, S3_Sound, S5_C02, S5_C02_Slope, S6_PIR, S7_PIR.
sensor_data$Room_Occupancy_Count <- as.numeric(sensor_data$Room_Occupancy_Count)
formula <- "Room_Occupancy_Count ~ S1_Temp + S2_Temp + S3_Temp + S4_Temp + S1_Light + S2_Light + S3_Light + S1_Sound + S2_Sound + S3_Sound + S5_CO2 + S5_CO2_Slope + S6_PIR + S7_PIR"
lm_model <- lm(formula, data = sensor_data)
summary(lm_model)
##
## Call:
## lm(formula = formula, data = sensor_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.95093 -0.12862 0.00908 0.08813 2.33055
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -8.601e+00 5.193e-01 -16.563 < 2e-16 ***
## S1_Temp 4.606e-01 4.018e-02 11.465 < 2e-16 ***
## S2_Temp 5.860e-02 9.479e-03 6.183 6.55e-10 ***
## S3_Temp 6.500e-01 2.819e-02 23.061 < 2e-16 ***
## S4_Temp -7.707e-01 2.031e-02 -37.938 < 2e-16 ***
## S1_Light 4.711e-03 1.746e-04 26.983 < 2e-16 ***
## S2_Light 1.007e-03 9.888e-05 10.181 < 2e-16 ***
## S3_Light 1.700e-03 1.134e-04 14.987 < 2e-16 ***
## S1_Sound 7.130e-02 1.314e-02 5.426 5.89e-08 ***
## S2_Sound 1.102e-01 1.557e-02 7.077 1.57e-12 ***
## S3_Sound -8.510e-02 1.016e-02 -8.377 < 2e-16 ***
## S5_CO2 1.625e-04 3.746e-05 4.338 1.45e-05 ***
## S5_CO2_Slope 2.024e-01 3.386e-03 59.761 < 2e-16 ***
## S6_PIR 1.725e-01 1.444e-02 11.947 < 2e-16 ***
## S7_PIR 4.335e-01 1.609e-02 26.943 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2959 on 10114 degrees of freedom
## Multiple R-squared: 0.8905, Adjusted R-squared: 0.8903
## F-statistic: 5874 on 14 and 10114 DF, p-value: < 2.2e-16
Based on the summary of the model we can observe:
Based on all the above points we can conclude that this is a better model for the given explanatory and response variables.
Let’s partition the data into two sets, one for training and one for testing, the train test split I have considered here is 0.7 which means 70% of data to training and 30% to testing.
set.seed(123)
train_index <- createDataPartition(sensor_data$Room_Occupancy_Count, p = 0.7, list = FALSE)
train_data <- sensor_data[train_index, ]
test_data <- sensor_data[-train_index, ]
lm_model <- lm(formula, data = train_data)
predicted <- predict(lm_model, newdata = test_data)
accuracy <- cor(predicted, test_data$Room_Occupancy_Count)^2
print(accuracy)
## [1] 0.8896556
Overall, we get an accuracy of ~ 89% which is a pretty accuracy. considering all the other results from the above cell, I feel this is a good model.
gg_diagnose(lm_model)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
Residual vs Fitted Value
gg_resfitted(lm_model) +
geom_smooth(se=FALSE)
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
From the above plot it is clear that linear model model doesn’t really suit our use case.
Linear model doesn’t really suit our use case as the target/response
variable although might look to be continuous are actually
categorical.
Since the target column Room_Occupancy_Count is categorical, it’s better to go for a classification technique rather than a linear model. Since there are four categories in the target column I will go for softmax transformation instead of a sigmoid transformation because sigmoid transformation works good only in the case of binary classification tasks.
multinom_model <- multinom(factor(Room_Occupancy_Count) ~ S1_Temp + S2_Temp + S3_Temp + S4_Temp + S1_Light + S2_Light + S3_Light + S1_Sound + S2_Sound + S3_Sound + S5_CO2 + S5_CO2_Slope + S6_PIR + S7_PIR, data = sensor_data)
## # weights: 64 (45 variable)
## initial value 14041.775584
## iter 10 value 2204.342652
## iter 20 value 1361.447425
## iter 30 value 670.690597
## iter 40 value 275.868263
## iter 50 value 151.700413
## iter 60 value 121.832944
## iter 70 value 116.361861
## iter 80 value 112.475785
## iter 90 value 110.722290
## iter 100 value 110.185607
## final value 110.185607
## stopped after 100 iterations
summary(multinom_model)
## Call:
## multinom(formula = factor(Room_Occupancy_Count) ~ S1_Temp + S2_Temp +
## S3_Temp + S4_Temp + S1_Light + S2_Light + S3_Light + S1_Sound +
## S2_Sound + S3_Sound + S5_CO2 + S5_CO2_Slope + S6_PIR + S7_PIR,
## data = sensor_data)
##
## Coefficients:
## (Intercept) S1_Temp S2_Temp S3_Temp S4_Temp S1_Light S2_Light
## 2 21.87952 -1.598909 -28.398829 -26.82408 53.39130 0.7576250 -0.13455900
## 3 88.28031 -1.853871 -8.531248 61.15831 -53.49753 0.5240298 0.09895107
## 4 -133.34636 17.186898 -7.458114 44.72936 -48.42625 -0.2784464 0.46533291
## S3_Light S1_Sound S2_Sound S3_Sound S5_CO2 S5_CO2_Slope S6_PIR
## 2 -0.35969307 -1.013038 -9.0746373 -4.7942700 0.03854205 8.521521 9.688373
## 3 0.02578905 -1.291806 -0.8935680 -0.1854925 -0.04128530 7.839662 5.195352
## 4 0.07530602 -1.997713 -0.4411527 -0.2809556 -0.02482201 8.602999 5.660996
## S7_PIR
## 2 2.291560
## 3 4.024230
## 4 4.559914
##
## Std. Errors:
## (Intercept) S1_Temp S2_Temp S3_Temp S4_Temp S1_Light S2_Light
## 2 0.002764958 0.06874802 0.08090234 0.08229162 0.08564311 0.05775641 0.1122372
## 3 0.004106097 0.12300454 0.19817428 0.12635251 0.11160684 0.07110592 0.1112535
## 4 0.001975019 0.07986672 0.21862902 0.08662038 0.06907264 0.17300588 0.1597986
## S3_Light S1_Sound S2_Sound S3_Sound S5_CO2 S5_CO2_Slope S6_PIR
## 2 0.03479643 0.4806437 0.09908168 0.02923712 0.007565350 0.4034594 0.4211150
## 3 0.02721398 0.4142987 0.33682774 0.26924266 0.005240530 0.1971412 0.3119308
## 4 0.02391356 0.4161612 0.30674281 0.26540352 0.002793867 0.2298846 0.3203523
## S7_PIR
## 2 0.5511972
## 3 0.3563774
## 4 0.3786651
##
## Residual Deviance: 220.3712
## AIC: 310.3712
predictions <- predict(multinom_model, newdata = test_data, type = "class")
accuracy <- mean(predictions == as.factor(test_data$Room_Occupancy_Count))
print(accuracy)
## [1] 0.9967084
The accuracy of the model is 99.6% which is a great accuracy but this can also lead to over fitting where the model has memorized the data in the dataset rather than understanding the underlying patterns.
conf_mat <- confusionMatrix(predictions, factor(test_data$Room_Occupancy_Count))
conf_mat
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 2489 0 0 0
## 2 0 134 1 0
## 3 0 2 199 3
## 4 1 0 3 206
##
## Overall Statistics
##
## Accuracy : 0.9967
## 95% CI : (0.994, 0.9984)
## No Information Rate : 0.8196
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9896
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 0.9996 0.98529 0.98030 0.98565
## Specificity 1.0000 0.99966 0.99824 0.99859
## Pos Pred Value 1.0000 0.99259 0.97549 0.98095
## Neg Pred Value 0.9982 0.99931 0.99859 0.99894
## Prevalence 0.8196 0.04477 0.06682 0.06880
## Detection Rate 0.8193 0.04411 0.06550 0.06781
## Detection Prevalence 0.8193 0.04444 0.06715 0.06912
## Balanced Accuracy 0.9998 0.99247 0.98927 0.99212
conf_table <- conf_mat$table
# Compute precision for each class
precision <- diag(conf_table) / rowSums(conf_table)
print(precision)
## 1 2 3 4
## 1.0000000 0.9925926 0.9754902 0.9809524
from the above confusion matrix it’s clear that multi nomial classification works well with all the classes.
Let’s go for another probabilistic approach with a bayesian technique
nb_model <- naive_bayes(factor(Room_Occupancy_Count) ~ S1_Temp + S2_Temp + S3_Temp + S4_Temp + S1_Light + S2_Light + S3_Light + S1_Sound + S2_Sound + S3_Sound + S5_CO2 + S5_CO2_Slope + S6_PIR + S7_PIR, data = train_data)
summary(nb_model)
##
## ================================= Naive Bayes ==================================
##
## - Call: naive_bayes.formula(formula = factor(Room_Occupancy_Count) ~ S1_Temp + S2_Temp + S3_Temp + S4_Temp + S1_Light + S2_Light + S3_Light + S1_Sound + S2_Sound + S3_Sound + S5_CO2 + S5_CO2_Slope + S6_PIR + S7_PIR, data = train_data)
## - Laplace: 0
## - Classes: 4
## - Samples: 7091
## - Features: 14
## - Conditional distributions:
## - Gaussian: 14
## - Prior probabilities:
## - 1: 0.8092
## - 2: 0.0456
## - 3: 0.0769
## - 4: 0.0684
##
## --------------------------------------------------------------------------------
predictions <- predict(nb_model, newdata = subset(test_data, select = c("S1_Temp", "S2_Temp", "S3_Temp", "S4_Temp", "S1_Light", "S2_Light", "S3_Light", "S1_Sound", "S2_Sound", "S3_Sound", "S5_CO2", "S5_CO2_Slope", "S6_PIR", "S7_PIR")))
accuracy <- mean(predictions == sensor_data$Room_Occupancy_Count)
## Warning in `==.default`(predictions, sensor_data$Room_Occupancy_Count): longer
## object length is not a multiple of shorter object length
## Warning in is.na(e1) | is.na(e2): longer object length is not a multiple of
## shorter object length
print(paste("Accuracy:", accuracy))
## [1] "Accuracy: 0.663540329746273"
With this model we get an accuracy of 66.3%. Let’s decide from multinomial and naive bayes by doing a precision check.
conf_mat <- confusionMatrix(predictions, factor(test_data$Room_Occupancy_Count))
conf_mat
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 2443 0 0 9
## 2 0 131 0 0
## 3 0 5 196 40
## 4 47 0 7 160
##
## Overall Statistics
##
## Accuracy : 0.9645
## 95% CI : (0.9572, 0.9707)
## No Information Rate : 0.8196
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.8911
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 0.9811 0.96324 0.96552 0.76555
## Specificity 0.9836 1.00000 0.98413 0.98091
## Pos Pred Value 0.9963 1.00000 0.81328 0.74766
## Neg Pred Value 0.9198 0.99828 0.99750 0.98265
## Prevalence 0.8196 0.04477 0.06682 0.06880
## Detection Rate 0.8041 0.04312 0.06452 0.05267
## Detection Prevalence 0.8071 0.04312 0.07933 0.07044
## Balanced Accuracy 0.9824 0.98162 0.97482 0.87323
conf_table <- conf_mat$table
precision <- diag(conf_table) / rowSums(conf_table)
print(precision)
## 1 2 3 4
## 0.9963295 1.0000000 0.8132780 0.7476636
From the above metrics we see that the model is very good at predicting Class 1 and class 2 and bad at predicting class 3 and class 4.
We have 2 models that look like they both might suit our use case, but we need to choose only one. Since multi-nomial classification seems to be a better fit for all the classes with high accuracy and better precision rates we can choose that.
sensor_data$DateTime <- as.POSIXct(paste(sensor_data$Date, sensor_data$Time), format="%d/%m/%y %H:%M:%S")
ggplot(sensor_data, aes(x = DateTime)) +
geom_line(aes(y = Mean_Temp, color = "Mean_Temp"), linewidth = 1) +
geom_line(aes(y = Mean_Light, color = "Mean_Light"), linewidth = 1) +
geom_line(aes(y = Mean_Sound, color = "Mean_Sound"), linewidth = 1) +
geom_line(aes(y = S5_CO2, color = "S5_CO2"), linewidth = 1) +
geom_line(aes(y = as.numeric(Room_Occupancy_Count), color = "Occupancy_Count"), linewidth = 1) +
scale_color_manual(name = "Variables",
values = c(Mean_Temp = "blue", Mean_Light = "green", Mean_Sound = "red", S5_CO2 = "purple", Occupancy_Count = "orange"),
labels = c("Temperature", "Light Intensity", "Sound Level", "CO2 Level", "Occupancy Count")) +
labs(x = "Time", y = "Value") +
theme_minimal()
From the above plot it’s there seems to be something wrong with data, it seems as if there is some data missing for some days. Let’s analyse
unique(sensor_data$Date)
## [1] "22/12/17" "23/12/17" "24/12/17" "25/12/17" "26/12/17" "10/01/18" "11/01/18"
It’s as I assumed, the dataset is missing dates from 27 DEC 2017 to 9 JAN 2018. Hence the flat line for those days.
The patterns in the plot clearly indicate the presence of day and night, columns such as Mean_Temperature, Mean_Light and Mean_Sound show a trend of becoming less in the night and more in the day.
The predictive room occupancy management system offers a comprehensive solution for smart building management, enabling efficient resource utilization, energy conservation, and enhanced occupant comfort. By leveraging advanced analytics and real-time monitoring, building operators can make data-driven decisions to optimize building performance and create more sustainable and comfortable environments.