s <- suppressPackageStartupMessages
s(library(zoo))
s(library(data.table))
s(library(lubridate))
s(library(knitr))
s(library(ggplot2))

1 Introduction

This notebook uses the outdoor temperature, indoor temperature and indoor airspeed to calculate:

# Parameters
n_hours_in_every_step <- 366 #Rolling mean will be based of the previous 14 days measurements

2 Method

2.1 Perform Average by Hour

The first step to the code is to construct the outdoor rolling mean. In order to do that, first the outdoor hourly measurement is averaged in hours.

# Load and rename columns for consistency -- no space and no upper case column names
outdoor <- fread("~/Github/erdl/thermal_comfort/adaptive_method/data/houses_outdoor_temperature.csv")
colnames(outdoor) <- c("timestamp","outdoor_temperature_f")

# Identify the right types for each column
outdoor$timestamp <- ymd_hms(outdoor$timestamp,tz="HST")
outdoor$outdoor_temperature_f <- as.numeric(outdoor$outdoor_temperature_f)
outdoor$ymd_h <- outdoor$timestamp

#Ensure timestamps are ordered. Database does not guarantee that.
outdoor <- outdoor[order(timestamp)]

# Reset the minute and second to 0, so we can group by ymd_h. 
minute(outdoor$ymd_h) <- 0
second(outdoor$ymd_h) <- 0
outdoor <- outdoor[,.(outdoor_temperature=mean(outdoor_temperature_f)),by="ymd_h"]
kable(head(outdoor))
ymd_h outdoor_temperature
2017-06-09 14:00:00 74.96525
2017-06-09 15:00:00 74.63462
2017-06-09 16:00:00 74.31538
2017-06-09 17:00:00 74.83525
2017-06-09 18:00:00 74.88375
2017-06-09 19:00:00 74.44012

2.2 Perform 14 Days Hourly Rolling Average

Next, the hourly temperature is used to calculate the 14 days temperature backwards.

# Invert table so slide window goes backwards
inverted_outdoor <- outdoor[order(-ymd_h)]

# Calculate rolling mean and reverts back the output from the rolling mean. 
rolling_mean_outdoor_temperature <- rev(rollapply(inverted_outdoor$outdoor_temperature,
                                            width=n_hours_in_every_step,
                                            FUN=mean))

#
outdoor <- outdoor[n_hours_in_every_step:.N]  
outdoor$rolling_mean_outdoor_temperature <- rolling_mean_outdoor_temperature
colnames(outdoor) <- c("timestamp","outdoor_temperature","rolling_mean_outdoor_temperature")

2.3 Simulation Data

To calculate the boundaries and acceptance, besides the outdoor rolling average, we also need the indoor temperature, and air speed. Because the tables are different, i.e. indoor’s temperature and airspeed are hourly, wherewas the outdoor was originally at a minute sampling rate, there is a chance after the rolling average is calculated the number of rows will not match between both tables. This script matches the minimum number of rows between them both.

# load indoor data
indoor <- fread("~/Github/erdl/thermal_comfort/adaptive_method/data/house2_livingroom_temperature.csv")[,.(reading_timestamp,value)]
colnames(indoor) <- c("timestamp","indoor_temperature")
indoor$timestamp <- ymd_hms(indoor$timestamp,tz="HST")
## Date in ISO8601 format; converting timezone from UTC to "HST".
# Identify the right types for each column
indoor$timestamp <- ymd_hms(indoor$timestamp,tz="HST")
indoor$outdoor_temperature_f <- as.numeric(outdoor$indoor_temperature)
indoor$ymd_h <- indoor$timestamp

#Ensure timestamps are ordered. Database does not guarantee that.
indoor <- indoor[order(timestamp)]

# Reset the minute and second to 0, so we can group by ymd_h. 
minute(indoor$ymd_h) <- 0
second(indoor$ymd_h) <- 0
indoor <- indoor[,.(indoor_temperature=mean(indoor_temperature)),by="ymd_h"]
colnames(indoor) <- c("timestamp","indoor_temperature")
dt <- merge(outdoor,indoor,by="timestamp")

# Add Rolling Mean Vector to Simulation Data Column-Wise. Notice this is NOT an inner join.
#min_rows <- min(length(rolling_temperature_f_mean),nrow(simulation_data))

#dt <- simulation_data[1:min_rows]
#dt$rolling_temperature_f_mean <- rolling_temperature_f_mean[1:min_rows]

2.4 Calculate boundaries

The comfort level boundaries are calculated using the outdoor rolling average adjusted by the indoor airspeed.

calculate_air_speed_adjustment <- function(in_air_speed){
  return(0.0153*in_air_speed+0.4333)
}

dt$upper_bound <- 0.31*dt$rolling_mean_outdoor_temperature + 60.5
dt$upper_bound_0_fpm <- dt$upper_bound + calculate_air_speed_adjustment(0)
dt$upper_bound_120_fpm <- dt$upper_bound + calculate_air_speed_adjustment(120)
dt$upper_bound_200_fpm <- dt$upper_bound + calculate_air_speed_adjustment(200)


dt$lower_bound <- 0.31*dt$rolling_mean_outdoor_temperature + 47.9 

2.5 Calculate Acceptance

With the comfort upper and lower bound, we then compare the indoor temperature against them.

dt$is_acceptable_80 <- NA_character_
dt[indoor_temperature > upper_bound]$is_acceptable_80 <- "Unacceptable Hot"
dt[indoor_temperature < lower_bound]$is_acceptable_80 <- "Unacceptable Cold"
dt[indoor_temperature > lower_bound & indoor_temperature < upper_bound]$is_acceptable_80 <- "Acceptable"

2.6 Calculate Degrees Off

In addition, how far higher or lower than the upper and lower bound respectively can also be calculated.

dt$degrees_off <- NA_real_

dt[indoor_temperature > upper_bound]$degrees_off <- dt[indoor_temperature > upper_bound]$indoor_temperature - dt[indoor_temperature > upper_bound]$upper_bound

dt[indoor_temperature < lower_bound]$degrees_off <- dt[indoor_temperature < lower_bound]$indoor_temperature - dt[indoor_temperature < lower_bound]$lower_bound

dt[indoor_temperature > lower_bound & indoor_temperature < upper_bound]$degrees_off <- 0

3 Output Table

Finally, we the output table containing all the calculated information.

kable(head(dt))
timestamp outdoor_temperature rolling_mean_outdoor_temperature indoor_temperature upper_bound upper_bound_0_fpm upper_bound_120_fpm upper_bound_200_fpm lower_bound is_acceptable_80 degrees_off
2017-10-06 12:00:00 77.18779 82.29032 84.73325 86.01000 86.44330 88.27930 89.50330 73.41000 Acceptable 0.0000000
2017-10-06 13:00:00 76.74112 82.27104 85.97350 86.00402 86.43732 88.27332 89.49732 73.40402 Acceptable 0.0000000
2017-10-06 14:00:00 76.38004 82.25701 87.15250 85.99967 86.43297 88.26897 89.49297 73.39967 Unacceptable Hot 1.1528280
2017-10-06 15:00:00 76.25629 82.24737 87.30000 85.99669 86.42999 88.26599 89.48999 73.39669 Unacceptable Hot 1.3033147
2017-10-06 16:00:00 76.15875 82.23663 87.22050 85.99336 86.42666 88.26266 89.48666 73.39336 Unacceptable Hot 1.2271438
2017-10-06 17:00:00 75.58500 82.22458 86.82250 85.98962 86.42292 88.25892 89.48292 73.38962 Unacceptable Hot 0.8328792

4 Plot

n_acceptable <- nrow(dt[is_acceptable_80 == "Acceptable"])
n_unacceptable <- nrow(dt[is_acceptable_80 != "Acceptable"])

st <- min(dt$timestamp)
et <- max(dt$timestamp)
p_start_time <- paste0(year(st),"-",month(st),"-",day(st))
p_end_time <- paste0(year(et),"-",month(et),"-",day(et))



p <- ggplot(data = dt) + 
  geom_point(aes(rolling_mean_outdoor_temperature,indoor_temperature,color=is_acceptable_80)) + 
  geom_line(aes(rolling_mean_outdoor_temperature,upper_bound_200_fpm),color="black") + 
  geom_line(aes(rolling_mean_outdoor_temperature,upper_bound_120_fpm),color="black") + 
  geom_line(aes(rolling_mean_outdoor_temperature,upper_bound_0_fpm),color="black") + 
  geom_line(aes(rolling_mean_outdoor_temperature,upper_bound),color="black") + 
  geom_line(aes(rolling_mean_outdoor_temperature,lower_bound),color="black") + 
  
  geom_text(aes(max(dt$rolling_mean_outdoor_temperature)+0.2,max(dt$upper_bound_200_fpm), label = "fpm 200", vjust = -1), size = 3) + 
  geom_text(aes(max(dt$rolling_mean_outdoor_temperature)+0.2,max(dt$upper_bound_120_fpm), label = "fpm 120", vjust = -1), size = 3) + 
  geom_text(aes(max(dt$rolling_mean_outdoor_temperature)+0.2,max(dt$upper_bound_0_fpm), label = "fpm 0", vjust = -1), size = 3) + 
  geom_text(aes(max(dt$rolling_mean_outdoor_temperature)+0.2,max(dt$upper_bound)-0.6, label = "no adj", vjust = -1), size = 3) + 
  geom_text(aes(max(dt$rolling_mean_outdoor_temperature)+0.2,max(dt$lower_bound), label = "no adj", vjust = -1), size = 3) + 
  
  
  theme_minimal() + 
  scale_colour_manual(values=c("Unacceptable Cold"="#0072B2","Acceptable"="#009E73","Unacceptable Hot"="#D55E00")) +
  ggtitle("Adaptive Method - House 2 Living Room", 
          subtitle = paste0(p_start_time," to ",p_end_time," | Acceptable: ",n_acceptable," ; Unacceptable: ",n_unacceptable)) + 
  ylab("Indoor Temperature (F)") + 
  xlab("Rolling Mean Outdoor Temperature (F)") 

p