""" HELLO, EVERYONE! Welcome to this Kaggle notebook! This notebook was created by **Group 6** for the module **BPS5229**. Group members: * Denise Huang * Shermaine Neo * Chen Shuting * Fan Baoli * Ting Shi An """ """ # **Building Data Genome Project 2 (BDG2) - Task 2** In reference to the task "[what are the factors that influence building energy consumption and how can we identify them?](https://www.kaggle.com/claytonmiller/buildingdatagenomeproject2/tasks?taskId=5939*)", the team investigates the possible factors that influence building electricity consumption of several selected educational buildings within universities. By analysing the findings, the team will then provides recommendations that may improve the consumption levels. """ """ # **1. Introduction** """ """ <div align="center"> <img src="https://www.evolutioninfosystem.com/wp-content/uploads/2019/09/booking-a-schedule-isometric-vector-illustration.jpg" width="600" /> </div> """ """ ## 1.1. University academic calendar schedules and its effects on electricity consumption Tertiary educational campuses are unique in terms of usage and consumption as they follow a certain schedule each year - the **university's academic calendar**. Futhermore, a university campus consists of different building uses and types, and this further adds to the complexity of the energy consumption levels. The common measures taken to reduce energy consumptions are usually focused on dealing with physical aspects of the building, such as efficient management of heating, cooling or lighting demand, using more energy-efficient systems or offsetting the consumption with on-site renewable energy generation. As a result, more behavioural orientated factors such as occupancy factors are often overlooked and not taken into account. """ """ With occupancy behaviour in mind, this project aims to investigate the effects of universities' academic calendar on the electrical usages of educational buildings. The team has identified two main methods that could potentially reduce consumption levels:## 1.2. Objectives With occupancy in mind, this project aims to investigate the effects of universities' academic calendar on the electrical usages of educational buildings. The team has identified two main methods that could potentially reduce consumption levels: 1. Switching from **single-session terms to multi-session terms** ([Section 3 - Single-session vs Multi-session terms](#3.-Single-session-vs-Multi-session-terms)) 2. Having a **later starting date** for the Fall term ([Section 8 - Adjustment to semester start dates](#8.-Alternative-Solution---Adjustment-to-Semester-Start-Date)) The effects of having these different configurations of calendar schedules will be investigated in the sections below. Given the complexity of these educational campuses, factors such as building usage type, building size and building age were subsequently analysed to give a better understanding on the impacts on energy consumption. By providing a better understanding through detailed analyses, the results could potentially aid in decision-making for universities that are considering a change in the acadamic calendar to reduce consumption levels. """ """ ## 1.3. Hypotheses Our hypotheses are as follows: **1. Multi-session terms will have a lower electricity consumption than single-session terms** A multi-session academic term is expected to have lower electricity consumption as compared to single-session term mainly due to the lower concentration of student in any given space. As a result, shaving the peak-load and spreads the electrical load across different days. To analyze this hypothesis, the team will be investigating two different universities, "Panther" and "Cockatoo". **2. Having a later starting date for the Fall term will result in a lower electricity consumption** Starting later would mean that a smaller proportion of the lessons are held during the summer period, where temperatures are higher. As such, starting the term only after the temperatures have lowered would result in lower electricity consumption. To analyze this hypothesis, the team will be investigating the consumption trends of "Cockatoo" and "Crow". """ """ # **2. Preparation for the analyses** """ """ ## 2.1 Importing and installing relevant libraries First, the notebook is set up with all the relevant libraries installed and imported for the analyses. """ # Install stargazer - a package to visualize regression results in academic paper format # This requires Kaggle's 'internet' option turned on under settings !pip install stargazer import pandas as pd from pandas import Timestamp import seaborn as sns import plotly.express as px import plotly.graph_objects as go import matplotlib.pyplot as plt import matplotlib as mpl from matplotlib.dates import MonthLocator import matplotlib.ticker as ticker from matplotlib.ticker import MaxNLocator import datetime from numpy.polynomial.polynomial import polyfit import numpy as np from scipy import stats import warnings from stargazer.stargazer import Stargazer from IPython.core.display import HTML """ ## 2.2. Importing and reading raw data files With the libraries imported, the team then imports the relevant raw data files. The electricity dataset has been cleaned, having outlier values removed and measurement units normalized. """ electricity = pd.read_csv("../input/buildingdatagenomeproject2/electricity_cleaned.csv", index_col = "timestamp", parse_dates=True) metadata = pd.read_csv("../input/buildingdatagenomeproject2/metadata.csv") weather = pd.read_csv("../input/buildingdatagenomeproject2/weather.csv", index_col = "timestamp", parse_dates=True) """ # **3. Single-session vs Multi-session terms** <div align="center"> <img src="https://image.freepik.com/free-vector/isometric-university-education-concept-with-professor-giving-lecture-students-auditorium-isolated_1284-39075.jpg" width="500" /> </div> To investigate the differences in electricity consumption during single-session and multi-session terms, the universities **Panther** and **Cockatoo** were selected. The map below shows the locations of these two universities. """ # Since all the coordinates are the same for each location, condense the dataset by taking the first row for each site. locations = metadata.loc[:,['site_id', 'lat', 'lng']].groupby('site_id').first() # Subset to Panther and Cockatoo only panther_cockatoo_locations = locations.loc[['Panther', 'Cockatoo'],:] # Plot map panther_cockatoo_map = go.Figure(go.Scattergeo(lat = panther_cockatoo_locations['lat'], lon = panther_cockatoo_locations['lng'], text = 'Location: ' + panther_cockatoo_locations.index, mode = 'markers', marker = dict(size = 10, color = ['#2a77db', '#ca0020']))) # Update title and land colour panther_cockatoo_map.update_layout(title_text = 'Locations of Panther and Cockatoo', geo = dict(landcolor = "rgb(218, 220, 224)")) # Update projection type to give a more 'globe' look panther_cockatoo_map.update_geos(projection_type = "natural earth") panther_cockatoo_map.show() """ ## 3.1 The first selected university - Panther The first selected university for this section of analysis is the **University of Central Florida (UCF)**, located in Orlando, Florida. Its site ID in the BDG2 dataset is **Panther**. UCF was chosen as it has two distinct curriculum structures throughout the year. During **Spring** and **Fall**, the university offers ***fixed-dates single-session lesson terms***. This means that all lessons are only offered once during the stipulated dates for the semester, and students attend lessons throughout the term. * Spring (2017): 9th January - 2nd May * Fall (2017): 21th August - 9th December However during **Summer**, the university offers a ***multi-session lesson term***. Instead of having lessons throughout the Summer term, students are able to choose one or more sessions as long as they are able to fufill their academic requirements. * Summer (2017) Session A: 15th May - 23rd June * Summer (2017) Session B: 26th June - 4th August * Summer (2017) Session C: 15th May - 4th August * Summer (2017) Session D: 15th May - 15th July """ """ > ### **3.1.1. Understanding Panther's academic calendar** To better understand Panther's academic calendar, the start and end dates of each term are visualized below. """ # Create a Gantt chart to visualize the schedule panther_schedule = pd.DataFrame([ dict(Term="Spring", Start='2017-01-09', Finish='2017-05-02', Type = 'Single-session'), dict(Term="Summer A", Start='2017-05-15', Finish='2017-06-23', Type = 'Multi-session'), dict(Term="Summer B", Start='2017-06-26', Finish='2017-08-04', Type = 'Multi-session'), dict(Term="Summer C", Start='2017-05-15', Finish='2017-08-04', Type = 'Multi-session'), dict(Term="Summer D", Start='2017-05-15', Finish='2017-07-15', Type = 'Multi-session'), dict(Term="Fall", Start='2017-08-21', Finish='2017-12-09', Type = 'Single-session') ]) fig = px.timeline(panther_schedule, x_start="Start", x_end="Finish", y="Term", color = 'Type', width=650, height=300, category_orders=dict(Term=["Spring", "Summer A", "Summer B", "Summer C", "Summer D", "Fall"]), color_discrete_map = {'Single-session': '#7ea5d9', 'Multi-session': '#2a77db'}, template = 'plotly_white') fig.show() """ > ### **3.1.2. Understanding Panther's annual air temperature fluctuations** Located in Florida with a sub-tropical climate, Panther is expected to have slight fluctuations in air temperature throughout the year. Florida experiences hot summers with temperatures peaking in August and mild winters with temperatures dipping in January and December. """ # Get weather data for Panther in 2017 panther_weather_2017 = weather[weather.site_id == "Panther"].truncate(before = "2017-01-01") # Convert hourly timestamps to monthly timestamps panther_weather_2017['month'] = panther_weather_2017.index.to_period('M').to_timestamp() # Convert hourly timestamps to daily timestamps panther_weather_2017['day'] = panther_weather_2017.index.to_period('D').to_timestamp() # Get a list of strings from Jan to Dec for the x axis labels months_str = pd.date_range(start = "2017-01-01", end = "2017-12-31", freq = 'M').strftime("%b") fig, ax = plt.subplots(figsize = (13, 8)) sns.set_theme(style = "whitegrid") # Plot the monthly mean air temperature with 95% confidence interval sns.lineplot(data = panther_weather_2017, x = "month", y = "airTemperature", ax = ax, linewidth = 4).set(xlabel = 'Month', ylabel = "Air Temperature (°C)", xticklabels = months_str) # Plot the daily mean air temperature with 95% confidence interval sns.lineplot(data = panther_weather_2017, x = "day", y = "airTemperature", ax = ax, color = "#92b2d4", alpha = 0.3).set(title = "Air Temperature in Panther (2017)", xlabel = 'Month', ylabel = "Air Temperature (°C)", xticklabels = months_str) # Sets the frequency of the ticks to be one month ax.xaxis.set_major_locator(MonthLocator()) ax.set_title("Air Temperature in Panther (2017)", fontsize=13) ax.xaxis.set_tick_params(labelsize=12) ax.yaxis.set_tick_params(labelsize=12) ax.xaxis.get_label().set_fontsize(12) ax.yaxis.get_label().set_fontsize(12) # Add custom legend to the plot plt.legend(["Monthly mean with 95% CI", "Daily mean with 95% CI"], loc=0, fontsize = 11) """ **An alternative visualization of the annual air temperature distribution** A ridgeline graph is plotted below to give a better understanding on the **distribution of temperatures per month** for Panther. A Ridgelineplot, formerly Joyplot, allows users study the distribution of a numeric variable for several groups. In order to do so, Seaborn FacetGrid object with kdeplots is required to generate a ridgeline graph. The code below is adapted from: https://www.python-graph-gallery.com/ridgeline-graph-seaborn """ # Create a copy of the weather dataframe and convert the month to numbers panther_weather_2017_2 = panther_weather_2017.copy() panther_weather_2017_2['month'] = panther_weather_2017_2['month'].dt.month # Define a dictionary to match the month number to its string month_dict = {1: 'January', 2: 'February', 3: 'March', 4: 'April', 5: 'May', 6: 'June', 7: 'July', 8: 'August', 9: 'September', 10: 'October', 11: 'November', 12: 'December'} # Convert the numbers to string panther_weather_2017_2['month'] = panther_weather_2017_2['month'].map(month_dict) # The monthly mean for each month is calculated and will be used later for colors in the FacetGrid plot. month_mean_series = panther_weather_2017_2.groupby('month')['airTemperature'].mean() panther_weather_2017_2['mean_month'] = panther_weather_2017_2['month'].map(month_mean_series) sns.set_theme(style="white", rc={"axes.facecolor": (0, 0, 0, 0)}) # Generate a color palette with Seaborn.color_palette() pal = sns.color_palette(palette='coolwarm', n_colors=12) # In the sns.FacetGrid class, the 'hue' argument is the one that is the one that will be represented by colors with 'palette' g = sns.FacetGrid(panther_weather_2017_2, row='month', hue = 'mean_month', aspect=13, height=0.75, palette=pal) # Add the densities kdeplots for each month g.map(sns.kdeplot, 'airTemperature', bw_adjust=1, clip_on=False, fill=True, alpha=1, linewidth=1.5) # Add a white line that represents the contour of each kdeplot g.map(sns.kdeplot, 'airTemperature', bw_adjust=1, clip_on=False, color="w", lw=2) # Add a horizontal line for each plot g.map(plt.axhline, y=0, lw=2, clip_on=False) # Loop over the FacetGrid figure axes (g.axes.flat) and add the month as text with the right color for i, ax in enumerate(g.axes.flat): ax.text(-15, 0.02, month_dict[i+1], fontweight='bold', fontsize=14, color=ax.lines[-1].get_color()) # Use matplotlib.Figure.subplots_adjust() function to get the subplots to overlap g.fig.subplots_adjust(hspace=-0.3) # Remove axes titles, yticks and spines g.set_titles("") g.set_ylabels("") g.set(yticks=[]) g.despine(bottom=True, left=True) # Plot the figure plt.setp(ax.get_xticklabels(), fontsize=14) plt.xlabel('Air temperature (°C)', fontsize=14) g.fig.suptitle('Daily average temperature in Panther per month', ha='center', fontsize=15, fontweight=15) plt.show() """ ## 3.2. Normalizing electricity consumption with air temperature It is widely understood that air temperature is a factor that affects electricity consumption, where extreme temperatures can potentially cause sharp increase in electricity usage. To ensure a fair comparison of electricity consumption between the educational buildings throughout the year, the team normalized the electricity consumption with air temperature. This is done using the concept of Heating Degree Hours and Cooling Degree Hours. """ """ > ### **3.2.1. Heating Degree Hours (HDHs) & Cooling Degree Hours (CDHs)** HDHs and CDHs are measurements used to assess the heating and cooling needs during different seasons of the year. It is commonly used to normalize buildings' energy consumption to compare buildings from different climatic zones or to compare a building across different seasons. Although the concept of heating and cooling degree days is more common, the team uses HDHs and CDHs instead to fully utilise available hourly data so that finer trends on a daily (rather than monthly) basis can be captured. HDH and CDHs are calculated relative to a base temperature, where it's assumed to require no heating or cooling. For the United States, the commonly used base temperature is 18.3°C. The steps to normalize electricity consumption with air temperature are as follows: 1. For each hour, calculate the absolute difference between the air temperature and the base temperature. Temperatures above the base temperature are CDHs since cooling is required, while temperatures below the base temperature as HDHs since heating is required. 2. For each day, obtain the sum of HDHs and CDHs. 3. Divide the daily energy consumption by the daily sum of HDHs and CDHS. As seen from the annual air temperature trend in Panther, the site mainly requires cooling throughout the year since it has a higher range of air temperatures (> 18.3°C). """ def temp_diff(x): ''' Calculates the temperature difference from the base temperature Parameter: x (float): The hourly temperature Returns: diff (float): The temperature difference ''' base_temp = 18.3 # Calculate either the HDH or CDH if x > base_temp: # Then calculate CDH diff = x - base_temp else: # Then calculate HDH diff = base_temp - x return diff def sum_hdh_cdh(hourly_temps): ''' Calculates the sum of the HDHs and CDHs in a day Parameter: hourly_temps (array): Hourly temperatures in a day Returns: sum_deg_hours (float): Sum of HDHs and CDHs in a day ''' sum_deg_hours = 0 # Finds the sum of the degree hours after calculating the temperature difference for x in hourly_temps: sum_deg_hours += temp_diff(x) return sum_deg_hours """ > ### **3.2.2. Calculating the sum of HDH and CDH for Panther** Using the daily sum of HDH & CDH, the team is able to normalize Panther's electricity consumption. """ # Use the weather normalization functions on Panther's air Temperature panther_sum_hdh_cdh = panther_weather_2017.airTemperature.to_frame().resample('D').apply(sum_hdh_cdh) # Rename the column panther_sum_hdh_cdh = panther_sum_hdh_cdh.rename(columns = {"airTemperature":"sum_hdh_cdh"}) """ > ### **3.2.3. Visualizing Panther's Sum of Heating And Cooling Hours in 2017** A visualization of total sum of HDH & CDH daily for 2017 is seen below. The graph peaks in the summer and falls during the winter as the site experiences hot summers but rather mild winters. """ # Convert hourly timestamps to monthly timestamps panther_sum_hdh_cdh['month'] = panther_sum_hdh_cdh.index.to_period('M').to_timestamp() fig, ax = plt.subplots(figsize=(13,8)) sns.set_theme(style = "whitegrid") # Plot on a monthly basis with 95% confidence interval sns.lineplot(data = panther_sum_hdh_cdh, x = "month", y = "sum_hdh_cdh", ax = ax, linewidth = 3).set(xlabel = 'Month', ylabel = "Sum of HDHs and CDHs", xticklabels = months_str) # Plot on a daily basis sns.lineplot(data = panther_sum_hdh_cdh, x = "timestamp", y = "sum_hdh_cdh", ax = ax, color = '#92b2d4', alpha = 0.5).set(title = "Panther's Sum of Heating and Cooling Hours (2017)", xlabel = 'Month', ylabel = "Sum of HDHs and CDHs", xticklabels = months_str) # Sets the frequency of the ticks to be one month ax.xaxis.set_major_locator(MonthLocator()) ax.set_title("Panther's Sum of Heating and Cooling Hours (2017)", fontsize=13) ax.xaxis.set_tick_params(labelsize=12) ax.yaxis.set_tick_params(labelsize=12) ax.xaxis.get_label().set_fontsize(12) ax.yaxis.get_label().set_fontsize(12) # Add custom legend to the plot plt.legend(["Monthly mean with 95% CI", "Daily mean"], loc=0, fontsize = 11) """ > ### **3.2.4. Anomaly Observed & Explained** From the figure above, an anomaly for the daily sum of HDHs and CDHs can be observed, with the sum of HDHs and CDHs being extremely high (>250 hours). Upon researching, the day corresponds to 8 January 2017. This coincides with the December 2017-January 2018 North American cold wave. This extreme weather event recorded extremely low temperatures, causing Panther to experience temperatures as low as 2°C on this day. More details can be found at: https://www.currentresults.com/Yearly-Weather/USA/FL/Orlando/extreme-annual-orlando-low-temperature.php """ """ > ### **3.2.5. Obtaining Panther's electricity data** The follow filters were applied on the intial electricity dataset to obtain the relevant data: * Only containing buildings from Panther * Only data from 2017-01-01 onwards * Only contains education buildings within Panther The resulting data was also resampled to the daily mean. """ # Obtain daily data for Panther in 2017 panther_elec_d = electricity.loc[:,electricity.columns.str.contains("Panther")].resample('D').mean().truncate(before = "2017-01-01") # Obtain data for only education buildings panther_edu_elec_d = panther_elec_d.loc[:, panther_elec_d.columns.str.contains("education")] """ > ### **3.2.6. Normalizing Panther's electricity data** The electricity usage of education buildings within Panther is normalized using the sum of HDH and CDHs to obtain weather-normalized data. """ # Normalize the electricity data panther_edu_elec_d_weather_norm = panther_edu_elec_d.apply(lambda x: x.div(panther_sum_hdh_cdh.sum_hdh_cdh), axis = 0) """ ## 3.3. Comparing the Multi-session and Single-session terms' electricity consumption """ """ > ### **3.3.1. Baseline Reference - Visualizing unnormalized electricity data** Serving as a baseline reference, the figure below shows the unnormalized electricity data for 2017 """ # Plot the original, unnormalized time series panther_edu_elec_avg = panther_edu_elec_d.apply(lambda x: x.mean(), axis = 1) fig, ax = plt.subplots(figsize=(13,8)) sns.set_theme(style = "whitegrid") sns.lineplot(data = panther_edu_elec_avg).set(xlabel = 'Month', ylabel = "Electricity (kWh)", xticklabels = months_str) # Sets the frequency of the ticks to be one month ax.xaxis.set_major_locator(MonthLocator()) ax.set_title("Panther Education Unnormalized Electricity Consumption (2017)", fontsize=13) ax.xaxis.set_tick_params(labelsize=12) ax.yaxis.set_tick_params(labelsize=12) ax.xaxis.get_label().set_fontsize(12) ax.yaxis.get_label().set_fontsize(12) # Draw lines to delineate each term ax.text(Timestamp('2017-02-23'), 195, "Spring", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-01-09'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-05-02'), color='black', linestyle='--', lw=1.5) ax.text(Timestamp('2017-06-14'), 195, "Summer", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-05-15'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-08-04'), color='black', linestyle='--', lw=1.5) ax.text(Timestamp('2017-10-10'), 195, "Fall", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-08-21'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-12-09'), color='black', linestyle='--', lw=1.5) """ > ### **3.3.2. Visualizing normalized electricity data** To understand the electricity consumption across the terms, the weather-normalized electricity for Panther's Education building is visualized in the figure below. """ # Plot the weather normalized time series panther_edu_elec_avg_weather_norm = panther_edu_elec_d_weather_norm.apply(lambda x: x.mean(), axis = 1) fig, ax = plt.subplots(figsize=(13,8)) sns.set_theme(style = "whitegrid") sns.lineplot(data = panther_edu_elec_avg_weather_norm).set(xlabel = 'Month', ylabel = "Electricity per degree hour (kWh/degree hour)", xticklabels = months_str) # Sets the frequency of the ticks to be one month ax.xaxis.set_major_locator(MonthLocator()) ax.set_title("Panther Education Weather-Normalized Electricity Consumption (2017)", fontsize=13) ax.xaxis.set_tick_params(labelsize=12) ax.yaxis.set_tick_params(labelsize=12) ax.xaxis.get_label().set_fontsize(12) ax.yaxis.get_label().set_fontsize(12) # Draw lines to delineate each term ax.text(Timestamp('2017-02-23'), 5.5, "Spring", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-01-09'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-05-02'), color='black', linestyle='--', lw=1.5) ax.text(Timestamp('2017-06-13'), 5.5, "Summer", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-05-15'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-08-04'), color='black', linestyle='--', lw=1.5) ax.text(Timestamp('2017-10-10'), 5.5, "Fall", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-08-21'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-12-09'), color='black', linestyle='--', lw=1.5) """ > ### **3.3.3. Visualizing the difference in electricity consumption across the terms** Although it is easily observable from the figure above that the summer term uses lesser energy than the other two terms, a boxplot is used to visualize the summarized the values for each term. This allows for a clearer and more definitive comparison, since the consumption levels fluctuate a lot throughout the days. """ # Change Series to Dataframe and rename the column panther_edu_elec_avg_weather_norm_df = panther_edu_elec_avg_weather_norm.to_frame() panther_edu_elec_avg_weather_norm_df = panther_edu_elec_avg_weather_norm_df.rename(columns = {0:'electricity'}) # Assign each date range to their respective terms panther_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-01-01', '2017-01-08'), 'term'] = 'Winter break 1' panther_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-01-09', '2017-05-02'), 'term'] = 'Spring' panther_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-05-03', '2017-05-14'), 'term'] = 'Spring break' panther_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-05-15', '2017-08-04'), 'term'] = 'Summer' panther_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-08-05', '2017-08-20'), 'term'] = 'Summer break' panther_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-08-21', '2017-12-09'), 'term'] = 'Fall' panther_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-12-10', '2017-12-31'), 'term'] = 'Fall break 2' # Keep only the terms panther_edu_elec_avg_weather_norm_df_terms = panther_edu_elec_avg_weather_norm_df[panther_edu_elec_avg_weather_norm_df.term.isin(['Spring', 'Summer', 'Fall'])] # Plot boxplot to compare the weather-normalized electricity consumption (without showing outliers) fig, ax = plt.subplots(figsize=(13,10)) sns.set_theme(style = "whitegrid") # Create custom palette palette2 = {'Spring':'#7ea5d9', 'Summer':'#2a77db', 'Fall':'#7ea5d9'} # Plot the boxplot. Outliers are not shown in the figure, and the means are represented by the white circles. sns.boxplot(data = panther_edu_elec_avg_weather_norm_df_terms, x = 'term', y = 'electricity', palette = palette2, showfliers = False, showmeans = True, meanprops = {"marker":"o", "markerfacecolor":"white", "markeredgecolor":"black", "markersize":"10"}).set(xlabel = "Terms", ylabel = 'Electricity per degree hour (kWh/degree hour)') ax.set_title("Weather-normalized electricity consumption per term (2017)", fontsize=13) ax.xaxis.set_tick_params(labelsize=12) ax.yaxis.set_tick_params(labelsize=12) ax.xaxis.get_label().set_fontsize(12) ax.yaxis.get_label().set_fontsize(12) """ ## 3.4. Conclusion - Energy consumption variation between the various types of terms in Panther By comparing the results above, it is clear that education buildings in Panther uses lesser energy during the Summer term. **This finding aligned with the team's hypothesis that multi-session terms would use lesser energy than single-session terms.** Analyzing a single university is not enough to determine if our hypothesis holds true. As such, another university with a similar academic calendar was analyzed. """ """ ## 3.5. Is this trend consistent among other universities? - Cockatoo To see if this trend holds true, the team analysed another university that has a multi-session summer term. The selected university is Cornell University (Cockatoo), located in Cornell, New York. Similar analyses that were conducted for Panther were conducted for Cockatoo to determine if the same trend (i.e. that multi-session terms use less energy) can be observed. """ """ > ### **3.5.1. Understanding Cockatoo's academic calendar** """ # Create a Gantt chart to visualize the schedule cockatoo_schedule = pd.DataFrame([ dict(Term="Spring", Start='2017-01-25', Finish='2017-05-23', Type = 'Single-session'), dict(Term="Summer 3W", Start='2017-05-31', Finish='2017-06-23', Type = 'Multi-session'), dict(Term="Summer 8W", Start='2017-06-12', Finish='2017-08-08', Type = 'Multi-session'), dict(Term="Summer 6W", Start='2017-06-26', Finish='2017-08-08', Type = 'Multi-session'), dict(Term="Fall", Start='2017-08-22', Finish='2017-12-14', Type = 'Single-session') ]) fig = px.timeline(cockatoo_schedule, x_start="Start", x_end="Finish", y="Term", color = 'Type', width=650, height=300, category_orders=dict(Term=["Spring", "Summer 3W", "Summer 8W", "Summer 6W", "Fall"]), color_discrete_map = {'Single-session': '#cf7a87', 'Multi-session': '#ca0020'}, template = 'plotly_white') fig.show() """ >### **3.5.2. Visualizing Cockatoo's unnormalized electricity data** """ cockatoo_weather_2017 = weather[weather.site_id == "Cockatoo"].truncate(before = "2017-01-01") # Use the weather normalization functions on cockatoo's air Temperature cockatoo_sum_hdh_cdh = cockatoo_weather_2017.airTemperature.to_frame().resample('D').apply(sum_hdh_cdh) # Rename the column cockatoo_sum_hdh_cdh = cockatoo_sum_hdh_cdh.rename(columns = {"airTemperature":"sum_hdh_cdh"}) # Obtain daily data for Panther in 2017 cockatoo_elec_d = electricity.loc[:,electricity.columns.str.contains("Cockatoo")].resample('D').mean().truncate(before = "2017-01-01") # Obtain data for only education buildings cockatoo_edu_elec_d = cockatoo_elec_d.loc[:, cockatoo_elec_d.columns.str.contains("education")] # Normalize the electricity data cockatoo_edu_elec_d_weather_norm = cockatoo_edu_elec_d.apply(lambda x: x.div(cockatoo_sum_hdh_cdh.sum_hdh_cdh), axis = 0) # Plot the original, unnormalized time series cockatoo_edu_elec_avg = cockatoo_edu_elec_d.apply(lambda x: x.mean(), axis = 1) fig, ax = plt.subplots(figsize=(13,8)) sns.set_theme(style = "whitegrid") sns.lineplot(data = cockatoo_edu_elec_avg, color = "#d96f6f").set(xlabel = 'Month', ylabel = "Electricity (kWh)", xticklabels = months_str) # Sets the frequency of the ticks to be one month ax.xaxis.set_major_locator(MonthLocator()) ax.set_title("Cockatoo Education Unnormalized Electricity Consumption (2017)", fontsize=13) ax.xaxis.set_tick_params(labelsize=12) ax.yaxis.set_tick_params(labelsize=12) ax.xaxis.get_label().set_fontsize(12) ax.yaxis.get_label().set_fontsize(12) # Draw lines to delineate each term ax.text(Timestamp('2017-03-15'), 222, "Spring", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-01-25'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-05-23'), color='black', linestyle='--', lw=1.5) ax.text(Timestamp('2017-06-20'), 222, "Summer", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-05-31'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-08-08'), color='black', linestyle='--', lw=1.5) ax.text(Timestamp('2017-10-10'), 222, "Fall", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-08-22'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-12-14'), color='black', linestyle='--', lw=1.5) """ > ### **3.5.3. Visualizing Cockatoo's normalized electricity data** """ # Plot the weather normalized time series cockatoo_edu_elec_avg_weather_norm = cockatoo_edu_elec_d_weather_norm.apply(lambda x: x.mean(), axis = 1) fig, ax = plt.subplots(figsize=(13,8)) sns.set_theme(style = "whitegrid") sns.lineplot(data = cockatoo_edu_elec_avg_weather_norm, color = "#d96f6f").set(xlabel = 'Month', ylabel = "Electricity per degree hour (kWh/degree hour)", xticklabels = months_str) # Sets the frequency of the ticks to be one month ax.xaxis.set_major_locator(MonthLocator()) ax.set_title("Cockatoo Education Weather-Normalized Electricity Consumption (2017)", fontsize=13) ax.xaxis.set_tick_params(labelsize=12) ax.yaxis.set_tick_params(labelsize=12) ax.xaxis.get_label().set_fontsize(12) ax.yaxis.get_label().set_fontsize(12) # Draw lines to delineate each term ax.text(Timestamp('2017-03-15'), 8.5, "Spring", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-01-25'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-05-23'), color='black', linestyle='--', lw=1.5) ax.text(Timestamp('2017-06-20'), 8.5, "Summer", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-05-31'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-08-08'), color='black', linestyle='--', lw=1.5) ax.text(Timestamp('2017-10-10'), 8.5, "Fall", color = 'black', fontsize=12) plt.axvline(pd.to_datetime('2017-08-22'), color='black', linestyle='--', lw=1.5) plt.axvline(pd.to_datetime('2017-12-14'), color='black', linestyle='--', lw=1.5) """ > ### **3.5.4. Visualizing the difference electricity consumption across the terms** It can be observed from the line plot above that the summer term has rather high consumption levels, showing the opposite trend as what was observed in Panther. The difference between the terms can be better compared using the box plot below. """ # Change Series to Dataframe and rename the column cockatoo_edu_elec_avg_weather_norm_df = cockatoo_edu_elec_avg_weather_norm.to_frame() cockatoo_edu_elec_avg_weather_norm_df = cockatoo_edu_elec_avg_weather_norm_df.rename(columns = {0:'electricity'}) # Assign each date range to their respective terms cockatoo_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-01-01', '2017-01-24'), 'term'] = 'Fall break 1' cockatoo_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-01-25', '2017-05-23'), 'term'] = 'Spring' cockatoo_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-05-24', '2017-05-30'), 'term'] = 'Spring break' cockatoo_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-05-31', '2017-08-08'), 'term'] = 'Summer' cockatoo_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-08-09', '2017-08-21'), 'term'] = 'Summer break' cockatoo_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-08-22', '2017-12-14'), 'term'] = 'Fall' cockatoo_edu_elec_avg_weather_norm_df.loc[pd.date_range('2017-12-15', '2017-12-31'), 'term'] = 'Fall break 2' # Keep only the terms cockatoo_edu_elec_avg_weather_norm_df_terms = cockatoo_edu_elec_avg_weather_norm_df[cockatoo_edu_elec_avg_weather_norm_df.term.isin(['Spring', 'Summer', 'Fall'])] # Plot boxplot to compare the weather-normalized electricity consumption (without showing outliers) fig, ax = plt.subplots(figsize=(13,10)) sns.set_theme(style = "whitegrid") # Create custom palette palette2 = {'Spring':'#d16d7a', 'Summer':'#ca0020', 'Fall':'#d16d7a'} # Plot the boxplot. Outliers are not shown in the figure, and the means are represented by the white circles. sns.boxplot(data = cockatoo_edu_elec_avg_weather_norm_df_terms, x = 'term', y = 'electricity', palette = palette2, showfliers = False, showmeans = True, meanprops = {"marker":"o", "markerfacecolor":"white", "markeredgecolor":"black", "markersize":"10"}).set(xlabel = "Terms", ylabel = 'Electricity per degree hour (kWh/degree hour)') ax.set_title("Weather-normalized electricity consumption per term (2017)", fontsize=13) ax.xaxis.set_tick_params(labelsize=12) ax.yaxis.set_tick_params(labelsize=12) ax.xaxis.get_label().set_fontsize(12) ax.yaxis.get_label().set_fontsize(12) """ ## 3.6. Conclusion - Findings from Cockatoo are different from Panther's It can be seen from the figures above that unlike Panther, electricity consumption for Cockatoo appears to be higher during the multi-session term as compared to the single-session terms. **This finding different from the team's hypothesis.** There are many other variables that come into play when analyzing electricity consumption, which could cause the difference this difference in findings between Panther and Cockatoo. As such, the team decided to analyze further, looking at other factors such as building type, building size, and building age. """ """ # **4. Type of education buildings and its effects on energy consumption** <div align="center"> <img src="https://img.freepik.com/free-vector/high-school-isometric-people-composition-with-class-room-interior-characters-female-teacher-students-illustration_1284-27270.jpg?size=626&ext=jpg" width="360" /> <img src="https://image.freepik.com/free-vector/isometric-biological-science-laboratory-template_1284-40638.jpg" width="300" /> </div> The complexity in energy consumption for higher educational campuses lies in the various types of building uses within these campuses. Taking for instance, the electricity usage of research buildings may not strictly follow the academic calendar since certain research and experiments have to be done on a daily basis. As such, the consumption in research buildings could potentially be similar throughout the year. With this, the team looks into the different types of education buildings and their electricity consumption in each term. """ """ ## 4.1. Visualizing weather-normalized electricity consumption for each education type: Research & Classroom The major types of education buildings in both Panther and Cockatoo are research laboratories and classrooms. """ """ > ### **4.1.1. Determining the number of classrooms and research labs in Panther and Cockatoo** The two universities have different ratios of classrooms to research laboratories. From the figures below, we can see that: * Panther has two times as many classrooms than research laboratories * Cockatoo has 1.2 times more research laboratories than classrooms """ warnings.filterwarnings("ignore") def melt_join(df): ''' Melts the dataframe containing several buildings, then joins the dataframe with only the relevant metadata information. Also calculates the age of the building based on the year built attribute, based on the current year (2021). Parameter: df (Dataframe): Dataframe containing the electricity consumption of each building in each column Returns: df_melt_joined (Dataframe): Dataframe containing consumption data melted into one column, with joined metadata information ''' df = df.reset_index() # Melt the dataframe df_melt= df.melt(id_vars = "timestamp", var_name="building_id",value_name="elec") # Subset the metadata to only the relevant columns metadata_join = metadata.loc[:,["building_id","yearbuilt","sqm","primaryspaceusage","sub_primaryspaceusage","site_id"]] # Join with the subsetted metadata df_melt_joined = df_melt.join(metadata_join.set_index("building_id"),on="building_id") # Calculate the building age based on the year 2021 df_melt_joined["building_age"] = 2021 - df_melt_joined["yearbuilt"] return df_melt_joined # Joining the metadata information with the electricity consumption dataset panther_edu_elec_d_weather_norm_melt_joined = melt_join(panther_edu_elec_d_weather_norm) # Barplot to show the number of classrooms compared to research (that have electricity data) fig, ax = plt.subplots(1,2, figsize = (13,5)) sns.set_theme(style = "whitegrid") sns.countplot(x="sub_primaryspaceusage", data=panther_edu_elec_d_weather_norm_melt_joined.groupby('building_id').first(), ax = ax[0]).set(title = "Number of classrooms and research labs in Panther", xlabel = "Usage type", ylabel = "Count") # Forces the y axis to be integers ax[0].yaxis.set_major_locator(MaxNLocator(integer=True)) # Changes the tick frequency of the y axis ax[0].yaxis.set_major_locator(ticker.MultipleLocator(2)) # Change the axis limits to ensure the axis is the same for both subplots ax[0].set_ylim(0, 24) # Joining the metadata information with the electricity consumption dataset cockatoo_edu_elec_d_weather_norm_melt_joined = melt_join(cockatoo_edu_elec_d_weather_norm) # Barplot to show the number of classrooms compared to research (that have electricity data) sns.countplot(x="sub_primaryspaceusage", data=cockatoo_edu_elec_d_weather_norm_melt_joined.groupby('building_id').first(), ax = ax[1], order = ['College Classroom', 'College Laboratory', 'Student Union']).set(title = "Number of classrooms, research labs and \n student union rooms in Cockatoo", xlabel = "Usage type", ylabel = "Count") # Forces the y axis to be integers ax[1].yaxis.set_major_locator(MaxNLocator(integer=True)) # Changes the tick frequency of the y axis ax[1].yaxis.set_major_locator(ticker.MultipleLocator(2)) """ > ### **4.1.2. Plotting the time-series of electricity consumption for each education type** With the time-series plotted below, the team observed that both universities exhibit different trends for the various types of education buildings. * For Panther, both the classroom and research types exhibit a rather gentle decrease during the summer term. * However in Cockatoo, the electricity consumption for research laboratories peak during the summer term, with the other two usage types only increase slightly. These trends can be clearly observed in section 4.2 as well. """ ## PANTHER # Plot the electricity consumption for each education type fig, ax = plt.subplots(2, 1, figsize=(12,15)) sns.set_theme(style="whitegrid") sns.lineplot(data=panther_edu_elec_d_weather_norm_melt_joined, x="timestamp", y="elec", hue="sub_primaryspaceusage",palette="tab10", linewidth=1.5, ax = ax[0]).set(ylabel = 'Electricity per degree hour (kWh/degree hour)', xlabel = 'Month', xticklabels = months_str) # Sets the frequency of the ticks to be one month ax[0].xaxis.set_major_locator(MonthLocator()) ax[0].legend(title = 'Usage type', loc = 'upper right', bbox_to_anchor=(1.18, 1)) # Draw lines to delineate the terms ax[0].text(Timestamp('2017-02-23'), 10.5, "Spring", color = 'black', fontsize=12) ax[0].axvline(pd.to_datetime('2017-01-09'), color='black', linestyle='--', lw=2) ax[0].axvline(pd.to_datetime('2017-05-02'), color='black', linestyle='--', lw=2) ax[0].text(Timestamp('2017-06-15'), 10.5, "Summer", color = 'black', fontsize=12) ax[0].axvline(pd.to_datetime('2017-05-15'), color='black', linestyle='--', lw=2) ax[0].axvline(pd.to_datetime('2017-08-04'), color='black', linestyle='--', lw=2) ax[0].text(Timestamp('2017-10-10'), 10.5, "Fall", color = 'black', fontsize=12) ax[0].axvline(pd.to_datetime('2017-08-21'), color='black', linestyle='--', lw=2) ax[0].axvline(pd.to_datetime('2017-12-09'), color='black', linestyle='--', lw=2) ax[0].set_title("Panther weather-normalized electricity consumption for each educational use type (2017)", fontsize=13) ax[0].xaxis.set_tick_params(labelsize=12) ax[0].yaxis.set_tick_params(labelsize=12) ax[0].xaxis.get_label().set_fontsize(12) ax[0].yaxis.get_label().set_fontsize(12) ## COCKATOO sns.lineplot(data=cockatoo_edu_elec_d_weather_norm_melt_joined, x="timestamp", y="elec", hue="sub_primaryspaceusage",palette="tab10", linewidth=1.5, ax = ax[1]).set(ylabel = 'Electricity per degree hour (kWh/degree hour)', xlabel = 'Month', xticklabels = months_str) # Sets the frequency of the ticks to be one month ax[1].xaxis.set_major_locator(MonthLocator()) ax[1].legend(title = 'Usage type', loc = 'upper right', bbox_to_anchor=(1.25, 1)) # Draw lines to delineate the terms ax[1].text(Timestamp('2017-03-10'), 18, "Spring", color = 'black', fontsize=12) ax[1].axvline(pd.to_datetime('2017-01-25'), color='black', linestyle='--', lw=1.5) ax[1].axvline(pd.to_datetime('2017-05-23'), color='black', linestyle='--', lw=1.5) ax[1].text(Timestamp('2017-06-21'), 18, "Summer", color = 'black', fontsize=12) ax[1].axvline(pd.to_datetime('2017-05-31'), color='black', linestyle='--', lw=1.5) ax[1].axvline(pd.to_datetime('2017-08-08'), color='black', linestyle='--', lw=1.5) ax[1].text(Timestamp('2017-10-10'), 18, "Fall", color = 'black', fontsize=12) ax[1].axvline(pd.to_datetime('2017-08-22'), color='black', linestyle='--', lw=1.5) ax[1].axvline(pd.to_datetime('2017-12-14'), color='black', linestyle='--', lw=1.5) ax[1].set_title("Cockatoo weather-normalized electricity consumption for each educational use type (2017)", fontsize=13) ax[1].xaxis.set_tick_params(labelsize=12) ax[1].yaxis.set_tick_params(labelsize=12) ax[1].xaxis.get_label().set_fontsize(12) ax[1].yaxis.get_label().set_fontsize(12) """ ## 4.2. Difference in electricity consumption per term for research and classroom To better understand the shift in electricity consumption for each term, boxplots and line plots were used. """ ## PANTHER # Join with the terms for each timestamp panther_edu_d_weather_norm_melt_joined2 = panther_edu_elec_d_weather_norm_melt_joined.merge(panther_edu_elec_avg_weather_norm_df['term'], how='left', on = 'timestamp') # Only keep the terms, remove the breaks panther_edu_d_weather_norm_melt_joined2_terms = panther_edu_d_weather_norm_melt_joined2[panther_edu_d_weather_norm_melt_joined2.term.isin(['Spring', 'Summer', 'Fall'])] # Plot boxplot to compare the weather-normalized electricity consumption (without showing outliers) fig, ax = plt.subplots(1, 2, figsize=(13,10)) sns.set_theme(style = "whitegrid") # Plot the boxplot. Outliers are not shown in the figure, and the means are represented by the white circles. sns.boxplot(data = panther_edu_d_weather_norm_melt_joined2_terms, x = 'term', y = 'elec', hue = 'sub_primaryspaceusage', palette = "tab10", ax = ax[0], showfliers = False, showmeans = True, meanprops = {"marker":"o", "markerfacecolor":"white", "markeredgecolor":"black", "markersize":"10"}).set(xlabel = "Terms", ylabel = 'Electricity per degree hour (kWh/degree hour)') # Change legend name and position ax[0].legend(title = 'Usage type', loc = 'upper right', fontsize = 11) # Change axis limits to ensure that the limits are the same for both subplots ax[0].set_ylim(0, 13) ax[0].set_title("Panther\'s weather-normalized \n electricity consumption per term (2017)", fontsize=13) ## COCKATOO # Join with the terms for each timestamp cockatoo_edu_d_weather_norm_melt_joined2 = cockatoo_edu_elec_d_weather_norm_melt_joined.merge(cockatoo_edu_elec_avg_weather_norm_df['term'], how='left', on = 'timestamp') # Only keep the terms, remove the breaks cockatoo_edu_d_weather_norm_melt_joined2_terms = cockatoo_edu_d_weather_norm_melt_joined2[cockatoo_edu_d_weather_norm_melt_joined2.term.isin(['Spring', 'Summer', 'Fall'])] # Plot the boxplot. Outliers are not shown in the figure, and the means are represented by the white circles. sns.boxplot(data = cockatoo_edu_d_weather_norm_melt_joined2_terms, x = 'term', y = 'elec', hue = 'sub_primaryspaceusage', palette = "tab10", ax = ax[1], showfliers = False, showmeans = True, meanprops = {"marker":"o", "markerfacecolor":"white", "markeredgecolor":"black", "markersize":"10"}).set(xlabel = "Terms", ylabel = 'Electricity per degree hour (kWh/degree hour)') # Change legend name and position ax[1].legend(title = 'Usage type', loc = 'upper right', fontsize = 11) # Change axis limits to ensure that the limits are the same for both subplots ax[1].set_ylim(0, 13) ax[1].set_title("Cockatoo\'s weather-normalized \n electricity consumption per term (2017)", fontsize=13) """ **Differing degrees of change** The line plot below makes it clearer that in Panther, both classroom and research types of buildings exhibit a similar degree of decrease from the Spring to Summer term, and a similar degree of increase from the Summer to Winter term. On the other hand, in Cockatoo, the consumption of research laboratories peak drastically during the Summer as compared to the other two types of buildings. """ # Visualize the steepness of the drops/increases fig, ax = plt.subplots(1,2, figsize=(11,5)) sns.set_theme(style = "whitegrid") # Assign the terms as a categorical variable with a certain order panther_edu_d_weather_norm_melt_joined2_terms['term'] = pd.Categorical(panther_edu_d_weather_norm_melt_joined2_terms['term'], categories = ['Spring', 'Summer', 'Fall'], ordered = True) cockatoo_edu_d_weather_norm_melt_joined2_terms['term'] = pd.Categorical(cockatoo_edu_d_weather_norm_melt_joined2_terms['term'], categories = ['Spring', 'Summer', 'Fall'], ordered = True) sns.lineplot(data = panther_edu_d_weather_norm_melt_joined2_terms.groupby(['term', 'sub_primaryspaceusage']).mean(), y = 'elec', x = 'term', hue = 'sub_primaryspaceusage', ax = ax[0]).set(ylabel = 'Electricity per degree hour (kWh/degree hour)', xlabel = 'Terms') ax[0].legend(title = 'Usage type', loc = 'upper right') # Change axis limits to ensure that the limits are the same for both subplots ax[0].set_ylim(0,4) ax[0].set_title("Panther\'s mean consumption per term", fontsize=13) sns.lineplot(data = cockatoo_edu_d_weather_norm_melt_joined2_terms.groupby(['term', 'sub_primaryspaceusage']).mean(), y = 'elec', x = 'term', hue = 'sub_primaryspaceusage', ax = ax[1], sort = False).set(ylabel = 'Electricity per degree hour (kWh/degree hour)', xlabel = 'Terms') ax[1].legend(title = 'Usage type', loc = 'upper right', bbox_to_anchor=(1.57, 1)) # Change axis limits to ensure that the limits are the same for both subplots ax[1].set_ylim(0,4) ax[1].set_title("Cockatoo\'s mean consumption per term", fontsize=13) """ ## 4.3. Conclusion - Electricity consumption of different building types **Research buildings consume more electricity than classrooms** From the analyses, it. is evident that research buildings consistently use more electricity than classroom buildings for the entire year. This finding is plausible as one will expect research buildings to have more sophisticated technology and equipment which require more electricity to operate. **Usage of research buildings may not be independent of the academic calendar** The team expects that research buildings will have a rather consistent electricity usage throughout the year, since these buildings may have critical equipment that are operating 24/7 regardless of the time of the year. However, results from Panther and Cockatoo show otherwise; that there are changes in electricity consumption between the terms. **Usage type and the academic calendar** Occupancy behaviour of the various buildings may differ across different universities, depending on the academic requirements based on the curriculum structure of the term. In Panther, the Spring, Summer and Fall terms are seen as 'normal' school terms where it is comupulsory for students to attend classes to fulfil their academic requirement. In Cockatoo, however, the Summer term is an optional semester, where students can decide if they would like to take lessons during this term to lighten their workload over the other two terms. It is speculated that as a result of Cockatoo's optional semester, the number of lessons occurring during Cockatoo's Summer term could be much lower as compared to Panther, which allows the staff and students of Cockatoo to avail themselves for more research work, therefore resulting in a large peak in electricity consumption for the research buildings. Furthermore, it is also possible that the optional semester resulted in an increase in the electricity consumption for student union buildings as students are now more available to conduct activities in those buildings. However the increase in the electricity consumptions for classrooms cannot be easily explained. The team can only speculate that it is due to an increase in student-organized activities using the classrooms as venues, hence the increase in electricity consumption. All-in-all, for universities that are considering to undertake a multi-session term structure, it is crucial that careful considerations are given to the type of buildings and the occupancy-behavior related factors such as the universities academic requirements are accounted for prior to the implementation. ---- A similar finding can be seen in the paper [*'Reducing university energy use beyond energy retrofitting: The academic calendar impacts'*](https://doi.org/10.1016/j.enbuild.2020.110647). The paper investigated the changes in energy use between a semester and trimester schedule. They observed that when the university switched to a trimester schedule, energy use in research buildings increased as the lessons were more spread out, thus the professors and students had more time to conduct research. """ """ # **5. Building size vs electricity consumption** <div align="center"> <img src="https://image.freepik.com/free-vector/isometric-university-big-composition-with-campus-yard-lanes-houses-with-shadows-football-playgro_1284-16290.jpg" width="500" /> </div> After considering the type of buildings, another potential factor that may be affecting electricity consumption is the floor area of the buildings. """ """ ## 5.1. Correlation between building size and electricity consumption for Panther and Cockatoo Using a joint plot which combines a scatter plot and histogram for each axis, the team analysed the correlation between building size and the weather-normalized electricity consumption values. A moderate to strong correlation between building size and electricity consumption can be observed, with Panther having a correlation coefficient of 0.666 and Cockatoo having a correlation coefficient of 0.805. """ # Calculate the mean electricity consumption for each building panther_edu_elec_mean_weather_norm = panther_edu_elec_d_weather_norm_melt_joined.groupby('building_id').mean() cockatoo_edu_elec_mean_weather_norm = cockatoo_edu_elec_d_weather_norm_melt_joined.groupby('building_id').mean() # Calculates various values for the best fit line slope, intercept, r_value, pv, se = stats.linregress(panther_edu_elec_mean_weather_norm['sqm'], panther_edu_elec_mean_weather_norm['elec']) # Plot jointplot p = sns.jointplot(x="sqm", y="elec", data=panther_edu_elec_mean_weather_norm, kind = 'reg') plt.suptitle('Panther - Building size vs Weather-normalized electricity consumption ' + '(r = %.3f' % r_value + ')', y = 1) p.set_axis_labels('Floor area (sqm)', 'Electricity per degree hour (kWh/degree hour)') # Calculates various values for the best fit line slope, intercept, r_value, pv, se = stats.linregress(cockatoo_edu_elec_mean_weather_norm['sqm'], cockatoo_edu_elec_mean_weather_norm['elec']) # Plot jointplot p = sns.jointplot(x="sqm", y="elec", data=cockatoo_edu_elec_mean_weather_norm, kind = 'reg', color = "red") plt.suptitle('Cockatoo - Building size vs Weather-normalized electricity consumption ' + '(r = %.3f' % r_value + ')', y = 1) p.set_axis_labels('Floor area (sqm)', 'Electricity per degree hour (kWh/degree hour)') """ ## 5.2. Effects of building size on changes in consumption between the terms As it has been established that building size could have an effect on energy consumption, the team went on to visualize the extents of change in consumption between each term for buildings of different sizes. """ ## PANTHER # Group the df by building id and term, then sort it according to the building size panther_ordered = panther_edu_d_weather_norm_melt_joined2_terms.groupby(['building_id', 'term']).mean().sort_values(by = 'sqm').reset_index() # Assign the terms as a categorical variable with a certain order panther_ordered['term'] = pd.Categorical(panther_ordered['term'], categories = ['Spring', 'Summer', 'Fall'], ordered = True) # Plot the lineplot for each building. As it is sorted by building size, more blue = smaller sized, more red = larger sized. fig, ax = plt.subplots(1, 3, figsize = (13, 10), gridspec_kw={'width_ratios': [1, 1, 0.05]}) sns.lineplot(data = panther_ordered, x = 'term', y = 'elec', hue = 'building_id', palette = 'RdBu_r', ax = ax[0]).set(ylabel = 'Electricity per degree hour (kWh/degree hour)', xlabel = 'Terms') ax[0].get_legend().remove() # Change the axis limits to ensure that the limits are the same for both subplots ax[0].set_ylim(0,14) ax[0].set_title("Panther\'s mean consumption per term", fontsize=13) ## COCKATOO # Group the df by building id and term, then sort it according to the building size cockatoo_ordered = cockatoo_edu_d_weather_norm_melt_joined2_terms.groupby(['building_id', 'term']).mean().sort_values(by = 'sqm').reset_index() # Assign the terms as a categorical variable with a certain order cockatoo_ordered['term'] = pd.Categorical(cockatoo_ordered['term'], categories = ['Spring', 'Summer', 'Fall'], ordered = True) # Plot the lineplot for each building. As it is sorted by building size, more blue = smaller sized, more red = larger sized. sns.lineplot(data = cockatoo_ordered, x = 'term', y = 'elec', hue = 'building_id', palette = 'RdBu_r', ax = ax[1]).set(ylabel = 'Electricity per degree hour (kWh/degree hour)', xlabel = 'Terms') ax[1].get_legend().remove() # Change the axis limits to ensure that the limits are the same for both subplots ax[1].set_ylim(0,14) ax[1].set_title("Cockatoo\'s mean consumption per term", fontsize=13) # Plot a colormap as the legend cmap = mpl.cm.RdBu_r norm = mpl.colors.Normalize(vmin=1, vmax=2) cb = fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), cax=ax[2], orientation='vertical') # Change the number of bins for the ticks tick_locator = ticker.MaxNLocator(nbins=1) cb.locator = tick_locator cb.update_ticks() # Change the labels for the ticks cb.ax.set_yticklabels(['Small floor area', 'Large floor area']) plt.subplots_adjust(wspace=0.3) """ ## 5.3. Conclusion - Building size and effect on electricity consumption Analysis of the line plots above informs us of the following: * **Buildings with a larger floor area generally consume more energy** * This can be seen from the general congregation of the red lines (representing buildings with large floor areas) at the upper parts of the plot, while the blue lines (representing buildings with small floor areas) are generally at the lower parts of the plot * This is consistent with the positive moderate correlation that was observed in the scatter plots * **Different building sizes may result in different extents of change in consumption between the terms** * This can be seen from the different steepness of the slopes between the terms. Generally, the red lines have steeper slopes, thus greater extents of change in consumption between the terms * On the other hand, the blue lines are generally rather flat, indicating that the consumption between the terms for smaller buildings do not change that much As seen from the results above, when implementing a multi-session term structure, universities that consist of many small buildings will see less of a difference in electricity consumption between the different terms. A university that has more larger buildings would be able to see a larger difference. """ """ # **6. Building age vs electricity consumption** <div align="center"> <img src="https://image.freepik.com/free-vector/city-downtown-concept_1284-12699.jpg" width="500" /> </div> Apart from building type and size, the team also checked against the building age for any possible effect on electricity consumption. """ """ ## 6.1. Normalizing the weather-normalized electricity consumption with floor area Since the buildings have different floor areas, it is best to get the energy use intensity (EUI) of each building before comparing their building age. """ def calculate_eui(elec_data): ''' Calculates the EUI of each building by dividing the electricity consumption by the floor area (sqm) Parameter: elec_data (Dataframe): Dataframe containing the electricity consumed for each building as each column Returns: eui (Dataframe): Dataframe containing the normalized electricity consumption (i.e. EUI) ''' eui = elec_data.copy() for col in elec_data.columns: sqm = metadata.loc[metadata.building_id == col,'sqm'].item() eui[col] = eui[col]/sqm return eui # Calculate EUI, join with metadata, then calculate the mean for each building. panther_edu_elec_mean_weather_norm_eui = melt_join(calculate_eui(panther_edu_elec_d_weather_norm)).groupby('building_id').mean() cockatoo_edu_elec_mean_weather_norm_eui = melt_join(calculate_eui(cockatoo_edu_elec_d_weather_norm)).groupby('building_id').mean() """ ## 6.2. Correlation between building age and electricity consumption for Panther and Cockatoo Similar to the previous section, a jointplot was used to understand the correlation between building age and electricity for both universities below. From the plot below, a weak to moderate correlation between building age and electricity consumption can been seen, with Panther having a correlation coefficient of -0.348 and Cockatoo having a correlation coefficient of 0.129. """ # Calculates various values for the best fit line slope, intercept, r_value, pv, se = stats.linregress(panther_edu_elec_mean_weather_norm_eui['sqm'], panther_edu_elec_mean_weather_norm_eui['elec']) # Plot jointplot p = sns.jointplot(x="building_age", y="elec", data=panther_edu_elec_mean_weather_norm_eui, kind = 'reg') plt.suptitle('Panther - Building age vs EUI ' + '(r = %.3f' % r_value + ')', y = 1) p.set_axis_labels('Building age', 'EUI per degree hour (kWh/degree hour/sqm)') # Calculates various values for the best fit line slope, intercept, r_value, pv, se = stats.linregress(cockatoo_edu_elec_mean_weather_norm_eui['sqm'], cockatoo_edu_elec_mean_weather_norm_eui['elec']) # Plot jointplot p = sns.jointplot(x="building_age", y="elec", data=cockatoo_edu_elec_mean_weather_norm_eui, kind = 'reg', color = 'red') plt.suptitle('Cockatoo - Building age vs EUI ' + '(r = %.3f' % r_value + ')', y = 1) p.set_axis_labels('Building age', 'EUI per degree hour (kWh/degree hour/sqm)') """ ## 6.3. Effects of building age on changes in consumption between the terms Although there is only a weak to moderate correlation between building age and electricity consumption, the relationship between the extent of change in consumption and building age was still analyzed using the line plots below. """ # Create the dataframe for EUI that is merged with the periods of the terms and breaks, and filter to only the terms panther_eui = melt_join(calculate_eui(panther_edu_elec_d_weather_norm)).merge(panther_edu_elec_avg_weather_norm_df['term'], how='left', on = 'timestamp') panther_eui_terms = panther_eui[panther_eui.term.isin(['Spring', 'Summer', 'Fall'])] cockatoo_eui = melt_join(calculate_eui(cockatoo_edu_elec_d_weather_norm)).merge(cockatoo_edu_elec_avg_weather_norm_df['term'], how='left', on = 'timestamp') cockatoo_eui_terms = cockatoo_eui[cockatoo_eui.term.isin(['Spring', 'Summer', 'Fall'])] ## PANTHER # Group the df by building id and term, then sort it according to the building size panther_ordered2 = panther_eui_terms.groupby(['building_id', 'term']).mean().sort_values(by = 'building_age').reset_index() # Assign the terms as a categorical variable with a certain order panther_ordered2['term'] = pd.Categorical(panther_ordered2['term'], categories = ['Spring', 'Summer', 'Fall'], ordered = True) # Plot the lineplot for each building. As it is sorted by building size, more blue = smaller sized, more red = larger sized. fig, ax = plt.subplots(1, 3, figsize = (13, 10), gridspec_kw={'width_ratios': [1, 1, 0.03]}) sns.lineplot(data = panther_ordered2, x = 'term', y = 'elec', hue = 'building_id', palette = 'RdBu_r', ax = ax[0]).set(ylabel = 'EUI per degree hour (kWh/degree hour/sqm)', xlabel = 'Terms') ax[0].get_legend().remove() # Change the axis limits to ensure that the limits are the same for both subplots ax[0].set_ylim(0,0.002) ax[0].set_title("Panther\'s mean consumption per term", fontsize=13) ## COCKATOO # Group the df by building id and term, then sort it according to the building size cockatoo_ordered2 = cockatoo_eui_terms.groupby(['building_id', 'term']).mean().sort_values(by = 'building_age').reset_index().dropna(subset = ['building_age'], axis = 0) # Assign the terms as a categorical variable with a certain order cockatoo_ordered2['term'] = pd.Categorical(cockatoo_ordered2['term'], categories = ['Spring', 'Summer', 'Fall'], ordered = True) # Plot the lineplot for each building. As it is sorted by building size, more blue = smaller sized, more red = larger sized. sns.lineplot(data = cockatoo_ordered2, x = 'term', y = 'elec', hue = 'building_id', palette = 'RdBu_r', ax = ax[1]).set(ylabel = 'EUI per degree hour (kWh/degree hour/sqm)', xlabel = 'Terms') ax[1].get_legend().remove() # Change the axis limits to ensure that the limits are the same for both subplots ax[1].set_ylim(0,0.002) ax[1].set_title("Cockatoo\'s mean consumption per term", fontsize=13) # Plot a colormap as the legend cmap = mpl.cm.RdBu_r norm = mpl.colors.Normalize(vmin=1, vmax=2) cb = fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), cax=ax[2], orientation='vertical') # Change the number of bins for the ticks tick_locator = ticker.MaxNLocator(nbins=1) cb.locator = tick_locator cb.update_ticks() # Change the labels for the ticks cb.ax.set_yticklabels(['Built recently', 'Built several \n years ago']) plt.subplots_adjust(wspace=0.45) """ ## 6.4. Conclusion - Building age vs electricity consumption Analysis of the line plots above informs us of the following: * **There is no clear relationship between building age and electricity consumption** * Unlike the line plots generated for building size, the above line plots do not show any distinct trend between building age and electricity consumption * This is consistent with what was observed in the scatter plots * **There is no clear trend between building age and the extent of change in consumption between the terms** * No observable pattern can be seen between building age and the steepness of the slopes in the line plot As such, building age is seen to have a weaker effect than building type and building size on electricity consumption. """ """ # **7. Cross-verification using regression** The team decided to cross-verify the previous findings using Ordinary Least Squares (OLS) regression. """ """ ## 7.1. Identifying dates that fall within the Summer term The summer terms dates are first identified and defined """ def multi_term_panther(d_time): ''' Identifies if the date falls in a summer term or a non-summer term for Panther Parameter: d_time (Timestamp): Date in Y-m-d format Returns: 0: Date is not within the summer term 1: Date is within the summer term ''' summer_s = datetime.datetime.strptime('2017-05-15','%Y-%m-%d') summer_e = datetime.datetime.strptime('2017-08-04','%Y-%m-%d') if d_time > summer_s and d_time < summer_e: return 1 else: return 0 def multi_term_cockatoo(d_time): ''' Identifies if the date falls in a summer term or a non-summer term for Cockatoo Parameter: d_time (Timestamp): Date in Y-m-d format Returns: 0: Date is not within the summer term 1: Date is within the summer term ''' summer_s = datetime.datetime.strptime('2017-05-31','%Y-%m-%d') summer_e = datetime.datetime.strptime('2017-08-08','%Y-%m-%d') if d_time > summer_s and d_time < summer_e: return 1 else: return 0 # Add a column to state if it is a summer term or not for i in range(len(panther_edu_elec_d_weather_norm_melt_joined)): panther_edu_elec_d_weather_norm_melt_joined.loc[panther_edu_elec_d_weather_norm_melt_joined.index[i],'summer'] = multi_term_panther(panther_edu_elec_d_weather_norm_melt_joined.timestamp[i]) for i in range(len(cockatoo_edu_elec_d_weather_norm_melt_joined)): cockatoo_edu_elec_d_weather_norm_melt_joined.loc[cockatoo_edu_elec_d_weather_norm_melt_joined.index[i],'summer'] = multi_term_cockatoo(cockatoo_edu_elec_d_weather_norm_melt_joined.timestamp[i]) """ ## 7.2. Constructing an OLS model for Panther """ # Construct an OLS model for Panther from statsmodels.formula.api import ols lm1 = ols('elec ~ sqm + building_age + sub_primaryspaceusage + summer', data=panther_edu_elec_d_weather_norm_melt_joined).fit() lm1.summary() """ > ### **7.2.1. Resolving the problem of multicollinearity and a high condition number in the model above** """ """ **Multicollinearity issue** There is a WARNING of a possible multicollinearity problem. To check this issue, the correlation matrix of independent variables was visualized. The matrix confirms that there is no strong correlation between the independent variables. """ d1 = pd.get_dummies(panther_edu_elec_d_weather_norm_melt_joined['sub_primaryspaceusage']) panther_edu_elec_d_weather_norm_melt_joined['Research']=d1['Research'] dfData = panther_edu_elec_d_weather_norm_melt_joined.loc[:,['building_age','sqm','summer','Research']].corr() sns.heatmap(dfData, annot=True, vmax=1, square=True, cmap="Blues") """ To further investigate the cause of the WARNING message, a regression model with a single independent variable was used for problem diagnosis. The result shows that the WARNING message is still present when sqm is used as the single independent variable. Since there is only one independent variable, it can be ruled out that multicollinearity is causing this WARNING. """ lm1 = ols('elec ~ sqm', data=panther_edu_elec_d_weather_norm_melt_joined).fit() lm1.summary() """ **High condition number** The initial OLS model in Section 7.2 has an extremely high condition number. This may be due to the high sensitivity in the fitted parameters when all the data are far to one side of x = 0, thus resulting in a high condition number. Notice that the sqm values are very high compared with other variables. To reduce the high-condition number, the sqm column was rescsaled with the formula: sqm_scaled = sqm/1000. After running the regression with the sqm_scaled column, the WARNING disappears. Reference: https://stats.stackexchange.com/questions/243000/cause-of-a-high-condition-number-in-a-python-statsmodels-regression """ panther_edu_elec_d_weather_norm_melt_joined['sqm_scaled'] = panther_edu_elec_d_weather_norm_melt_joined['sqm']/1000 cockatoo_edu_elec_d_weather_norm_melt_joined['sqm_scaled'] = cockatoo_edu_elec_d_weather_norm_melt_joined['sqm']/1000 lm1 = ols('elec ~ sqm_scaled + building_age + sub_primaryspaceusage + summer ', data=panther_edu_elec_d_weather_norm_melt_joined).fit() lm1.summary() lm2 = ols('elec ~ sqm_scaled + building_age + sub_primaryspaceusage ', data=panther_edu_elec_d_weather_norm_melt_joined).fit() lm3 = ols('elec ~ sqm_scaled + building_age + summer', data=panther_edu_elec_d_weather_norm_melt_joined).fit() lm4 = ols('elec ~ sqm_scaled + sub_primaryspaceusage + summer ', data=panther_edu_elec_d_weather_norm_melt_joined).fit() lm5 = ols('elec ~ building_age + sub_primaryspaceusage + summer ', data=panther_edu_elec_d_weather_norm_melt_joined).fit() """ > ### **7.2.2. Displaying the results of Panther's OLS model** """ stargazer = Stargazer([lm1,lm2, lm3, lm4, lm5]) HTML(stargazer.render_html()) """ **Interpretation of regression results for Panther** **Regression Model 1:** 40.3% of the variation of electricity consumption can be explained by the four variables***(building age, building size, building type, curriculum type (i.e. whether it is a summer (multi-session) term).*** On average, - For every one year increment in building age, 0.012 kWh/degree hour less electricity is used daily - For every 1 sqm/1000 increase in floor area, electricity consumption increases by 0.248 kWh/degree hour - Research buildings consume 1.123 kWh/degree hour more