Abstract
As part 2/3 of a series of highlights for the “Data-driven approach using machine learning techniques to analyse energy consumption & CO2 emission of corporate electric vehicles for the up-scaling of low carbon mobility Berlin”, this page describes the 80 EVs charging data by merging with the hourly electricity grid data for 2015 and 2016. By using the KPI gCO2/km, carbon emissions related to the charging (and therefore the operating) of EVs are analysed. The results show that EVs are not operating with zero emission, once the charging is taken into account, although the EVs emits almost 2.13 times less emission in average compared to a similar ICE car model. The analysis simulates 2 charging scenarios based on “Renewable energy-oriented charging”. This smart charging strategy is simulated for “lowest carbon emission scenario” by shifting charge events to the daily lowest gCO2/kWh hour in the grid. On the other hand, the research also simulates the “highest carbon emission scenario” by shifting charge events to the daily highest gCO2/kWh hour in the grid. The difference of carbon emission in both the lowest and highest scenario are compared to the carbon emission from the EV data in real world.
Part 1: Driving data analytics of 80 EVs
Part 3: Machine Learning on 80 EVs driving behaviour
To analyse the carbon intensity of the EVs, this analysis uses the KPI of gram CO2 per kilometer, gCO2/km, by using the charging data of the 80 EVs, after pre-processing, cleaning and manipulation.
# General, data manipulation
library(dplyr)
library(lubridate)
library(ggplot2)
library(tidyr)
# Spatial data analysis
library(sp) # create spatial data frame
library(rgdal) # for projecting coordinates
library(maptools)
library(leaflet)
Using the hourly electricity data from Agora Energiewende, German electricity production for the year of 2015 is quantified into each generating sources (GigaWatt/hour).
agora2015 <- read.csv("../data/Agorameter_2015.csv")
elec2015 <- agora2015[,c(1:16)]
elec2015 <- elec2015[,(-15)]
# convert date.time column into standard date and time format
elec2015$Date.Time <- mdy_hm(elec2015$Date.Time)
str(elec2015)
## 'data.frame': 8760 obs. of 15 variables:
## $ Year : int 2015 2015 2015 2015 2015 2015 2015 2015 2015 2015 ...
## $ Month : int 1 1 1 1 1 1 1 1 1 1 ...
## $ Day : int 1 1 1 1 1 1 1 1 1 1 ...
## $ Hour : int 1 2 3 4 5 6 7 8 9 10 ...
## $ Date.Time : POSIXct, format: "2015-01-01 00:00:00" "2015-01-01 01:00:00" ...
## $ Biomass : int 5112 5112 5112 5112 5112 5112 5112 5112 5112 5112 ...
## $ Hydro : int 348 350 346 341 337 337 338 334 328 322 ...
## $ Wind.onshore : int 10137 10327 10593 10614 10724 10802 11310 12027 11896 11698 ...
## $ Wind.offshore: int 0 0 0 0 0 0 0 0 0 0 ...
## $ PV : int 0 0 0 0 0 0 0 0 59 852 ...
## $ Nuclear : int 11514 11920 11859 11860 11796 11538 11141 10881 11097 10909 ...
## $ Lignite : int 17583 17206 16742 15964 16016 15357 14274 13861 14144 14611 ...
## $ Hard.Coal : int 6427 5419 5275 5493 5337 5338 5430 5484 5402 5462 ...
## $ Natural.Gas : int 8011 7727 7650 7556 7601 7713 7714 7746 7783 7763 ...
## $ Others : int 1947 1944 1951 1954 1938 1908 1881 1925 1899 1898 ...
Each of the generation sources are then categorised into “conventional” and “renewable” energy sources.
# use tidyr gather command to turn energy sources into 1 column (wide -> long)
KeepThese <- colnames(elec2015)[1:5]
elec2015long <- gather(elec2015, sources, kWh, -one_of(KeepThese))
elec2015long$ren.con <- "renewable"
elec2015long$ren.con[which(elec2015long$sources %in% c("Hard.Coal","Lignite","Natural.Gas","Nuclear","Others"))] <- "conventional"
In order to calculate the KPI of gCO2/km among the EVs, the emission factors of each energy generating sources were used. Emission factors are the amount of gCO2 that are emitted while generating 1kWh of energy from that particular source, gCO2/kWh. Usually, fossil-based sources have higher emission factors compared to renewable energy sources. This analysis uses GEMIS, an open source emission factor model from the Institute for Sustainable Analysis and Strategy. The emission factors are selected to match the electricity production and generation energy sources, with the following assumptions:
The following table summarises the emission factors used for the calculations of carbon emission in this research:
## sources gCO2.kWh
## 1 Hard.Coal 895.04
## 2 Lignite 1007.87
## 3 Natural.Gas 410.13
## 4 Nuclear 55.48
## 5 Hydro 2.79
## 6 Wind.onshore 9.34
## 7 Wind.offshore 9.34
## 8 PV 96.16
## 9 Biomass 57.12
## 10 Others 286.34
By merging the hourly electricity production with the respective energy generating sources, the hourly gCO2/kWh for 2015 is produced. The black areas indicate hours that the grid is more carbon intensive than the green areas.
# create column for each source's emission factor
elec2015_CO2 <- merge(elec2015long, emfac, by="sources")
elec2015_CO2 <- elec2015_CO2[,c(2:6,1,7:9)]
# create column for CO2 emission for each sources (unit: tCO2)
elec2015_CO2$gCO2_h <- round(elec2015_CO2$kWh*elec2015_CO2$gCO2.kWh,2)
elec2015_CO2 <- arrange(elec2015_CO2, Date.Time)
emfac_hour <- elec2015_CO2 %>%
group_by(Date.Time) %>%
summarise(totalkWh = sum(kWh), gCO2 = sum(gCO2_h)) %>%
mutate(gCO2_kWh=round((gCO2_kWh=gCO2/totalkWh),2))
ggplot(emfac_hour, aes(x=Date.Time, y=gCO2_kWh, fill=gCO2_kWh)) +
geom_bar(stat="identity", alpha=0.8) +
scale_fill_continuous(low="green", high="black") +
stat_smooth(method="auto") +
ylab("CO2 emission (gCO2/kWh)") + xlab("Date") +
ggtitle("Hourly CO2 emission of Electricity Grid in Germany") +
theme(axis.text=element_text(size=12))
In order to cross-relate the EV’s actual CO2 emission during the respective charge, the hourly electricity production is merged with the respective emission factor. Due to availability of 2016 electricity data, this analysis uses the electrictiy data from 2015 for 2016.
# create table just with date_hour and emission factor
emfac_2015 <- emfac_hour[,c("Date.Time", "gCO2_kWh")]
colnames(emfac_2015)[1] <- "date_hour"
# emfac for 2016 (using emfac for 2015, shift the extra day in 29th February 2015 to end of year)
emfac_2016 <- emfac_2015
emfac_2016$date_hour <- seq(as.POSIXct("2016-01-01 00:00:00"), as.POSIXct("2016-12-30 22:00:00"), by="hour")
emfac_1516 <- rbind(emfac_2015, emfac_2016)
summary(emfac_1516$gCO2_kWh)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 216.1 408.9 480.6 468.3 537.7 638.3
gCO2/kmDeveloping the KPI of gCO2/km by merging gCO2/kWh from the grid, with kWh/100km from the 80 EVs.
BMW_CO2 <- merge(BMW_charge_complete, emfac_1516, by="date_hour")
BMW_CO2$gCO2 <- round(BMW_CO2$soc_final*BMW_CO2$gCO2_kWh,2)
BMW_CO2_sum <- BMW_CO2 %>%
group_by(vehicle_id) %>%
summarise(total_gCO2 = sum(gCO2))
BMW_CO2_km <- merge(BMW_CO2_sum, BMW_distance[,c(1,2,4)], by="vehicle_id")
BMW_CO2_km$gCO2_km <- round(BMW_CO2_km$total_gCO2/BMW_CO2_km$total_km,2)
summary(BMW_CO2_km$gCO2_km)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 46.73 58.63 65.60 65.53 71.35 102.20
ggplot(BMW_CO2_km, aes(x=factor(REX_BEV), y=gCO2_km, fill=factor(REX_BEV))) +
geom_boxplot() +
xlab("EV type") +
ylab("gCO2/km") +
ggtitle("Average carbon emission for EV types (gCO2/km)") +
scale_fill_discrete(name="EV type:") +
theme(axis.text=element_text(size=12))
The boxplot shows that the 2 types of EVs have almost similar median gCO2/km.
EVs start to charge the moment they are plugged into the charging infrastructure, and stop charging when it is fully charged or unplugged. Öko-Institut e.V describes in detail smart charging strategies which involves charging only at a specific time frame, although the EV is plugged in during the whole period. Following are three possible smart charging strategies:
This analysis simulates the potential of “Renewable energy-oriented charging”.
gCO2/kWh hourDescribing the highest occurance of daily minimum gCO2/kWh hour in the grid for 2015-2016.
emfac_1516_lowest <- emfac_1516 %>%
group_by(year, month, day) %>%
filter(gCO2_kWh == min(gCO2_kWh)) %>%
arrange(year, month, day) %>%
mutate(gCO2_kWh_low = gCO2_kWh)
## which hour have the most lowest gCO2/kWh in the day
emfac_1516_lowest_hour <- emfac_1516_lowest %>%
group_by(hour) %>%
summarise(count = n())
# keep hours that has 0 counts
h24 <- data.frame(hour=c(0:23), zero=0)
emfac_1516_lowest_hour <- right_join(emfac_1516_lowest_hour, h24, by="hour")
emfac_1516_lowest_hour$count[is.na(emfac_1516_lowest_hour$count)] <- 0
emfac_1516_lowest_hour$zero <- NULL
ggplot(data=emfac_1516_lowest_hour, aes(x=factor(hour), y=count, fill=count)) +
geom_bar(stat="identity") +
geom_text(aes(label=count), vjust=-0.25, size=3) +
ggtitle("Lowest daily gCO2/kWh hour count for 2015 & 2016") +
scale_x_discrete("Hours of the day") +
scale_y_continuous("Count") +
theme(legend.position="none") +
theme(axis.text=element_text(size=12))
The histogram describes the count of each particular hour, that is the lowest gCO2/kWh hour for the day, across the year of 2015 and 2016. It shows that during the hours of 11-13 and 23 have relatively high occurance of lowest hourly gCO2/kWh of the day.
gCO2/kWh hourDescribing the highest occurance of daily maximum gCO2/kWh hour in the grid for 2015-2016.
emfac_1516_highest <- emfac_1516 %>%
group_by(year, month, day) %>%
filter(gCO2_kWh == max(gCO2_kWh)) %>%
arrange(year, month, day) %>%
mutate(gCO2_kWh_high = gCO2_kWh)
## which hour have the most lowest gCO2/kWh in the day
emfac_1516_highest_hour <- emfac_1516_highest %>%
group_by(hour) %>%
summarise(count = n())
# keep hours that has 0 counts
emfac_1516_highest_hour <- right_join(emfac_1516_highest_hour, h24, by="hour")
emfac_1516_highest_hour$count[is.na(emfac_1516_highest_hour$count)] <- 0
emfac_1516_highest_hour$zero <- NULL
ggplot(data=emfac_1516_highest_hour, aes(x=factor(hour), y=count, fill=count)) +
geom_bar(stat="identity") +
geom_text(aes(label=count), vjust=-0.25, size=3.0) +
ggtitle("Highest daily gCO2/kWh hour for 2015 & 2016 ") +
scale_x_discrete("Hours of the day") +
scale_y_continuous("Count") +
theme(legend.position="none") +
theme(axis.text=element_text(size=12))
As for the hightest
gCO2/kWh hour for the day, across the year of 2015 and 2016, the histogram shows that the grid has high counts of daily highest gCO2/kWh during the evening hours as well as midnight.
Compared to the actual charging behaviours of the 80 EVs, the following diagram compares the lowest and highest gCO2/km charging scenario.
ggplot(gCO2_all_long, aes(x=factor(variables), y=score, fill=factor(variables))) +
geom_boxplot() +
xlab("Scenarios") +
ylab("gCO2/km") +
ggtitle("Average gCO2/km for 80 EVs in 3 scenarios") +
scale_fill_discrete(name="Emission Scenarios") +
theme(axis.text=element_text(size=12))
By shifting the charge to the lowest gCO2/kWh hour for every day in the project period, a maximum of \(18.36%\) of carbon emissions could be reduced, while charging at the highest gCO2/kWh hour would result in an increase of \(20.88%\) of carbon emissions.
| Scenarios | Diff. to Actual (Min.) | Diff. to Actual (Max.) |
|---|---|---|
| Lowest grid gCO2/kWh | 7.72% | 18.36% |
| Highest grid gCO2/kWh | 7.55% | 20.88% |
The visualisation describes the cluster of charging events of the 80 EVs throughout the project period. By zooming out, in addition to the majority of charge events taking place in Berlin and Brandenburg area, there are also a minority of charge events that took place in the area of Munich and Cologne.
# colour scheme for gCO2/km
col_gCO2_km <- colorNumeric(
palette = c("darkgreen", "navyblue", "red3"),
domain = BMW_GPS_complete$gCO2_km
)
popup_fast_std <- paste0("Vehicle ID: ", BMW_GPS_complete$vehicle_id,
"<br><strong>gCO2/km: </strong>", BMW_GPS_complete$gCO2_km,
"<br>charger type: ", BMW_GPS_complete$charger_type
)
leaflet(BMW_GPS_complete) %>%
setView(lng = 13.40, lat = 52.52, zoom = 10) %>%
addProviderTiles("Stamen.Toner",
options=providerTileOptions(opacity=0.35)) %>%
addProviderTiles("Stamen.TonerLabels") %>%
addPolygons(data=plr, weight=1, color = "grey", fillColor = "lightblue") %>%
addCircleMarkers(weight = 1,
color = ~col_gCO2_km(gCO2_km),
popup = popup_fast_std,
clusterOptions = markerClusterOptions()
)
gCO2/kmThe carbon intensity of the 80 EVs’ charge events are visualised according to the colour scheme, i.e. the lower the carbon intensity, the greener the spot on the map.
# colour scheme for gCO2/km
col_gCO2_km2 <- colorNumeric(
palette = c("limegreen", "navy", "red3"),
domain = BMW_GPS_complete$gCO2_km
)
popup_gCO2_km <- paste0("Vehicle ID: ", BMW_GPS_complete$vehicle_id,
"<br><strong>gCO2/km: </strong>", BMW_GPS_complete$gCO2_km, " gCO2/km"
)
leaflet(BMW_GPS_complete) %>%
setView(lng = 13.40, lat = 52.52, zoom = 10) %>%
addProviderTiles("Stamen.Toner",
options=providerTileOptions(opacity=0.35)) %>%
addProviderTiles("Stamen.TonerLabels") %>%
addPolygons(data=plr, weight=1, color = "grey", fillColor = "#FFFFCC") %>%
addCircleMarkers(popup = popup_gCO2_km,
color = ~col_gCO2_km2(gCO2_km),
stroke = FALSE,
fillOpacity = 0.5,
radius = ~sqrt(gCO2_km)
) %>%
addLegend("topright",
pal=col_gCO2_km2,
values = ~gCO2_km,
title="gCO2/km <br> (Charge Events)",
opacity = 1
)