%pip install -q folium
%pip install -q matplotlib
%pip install -q pandas
%pip install -q requests
%pip install -q seaborn
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('seaborn')
print('Modules are imported.')
Modules are imported.
df = pd.read_json('./data/moatsystems_ev_charging_station.json')
df.head()
id | chargeDeviceID | reference | name | latitude | longitude | subBuildingName | buildingName | buildingNumber | thoroughfare | ... | connector8Type | connector8RatedOutputKW | connector8OutputCurrent | connector8RatedVoltage | connector8ChargeMethod | connector8ChargeMode | connector8TetheredCable | connector8Status | connector8Description | connector8Validated | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 7db2224a-8fef-468a-897e-6f92efe023fe | 9c8661befae6dbcd08304dbf4dcaf0db | SC22 | Little Victoria St Car Park - Socket 2 | 54.592703 | -5.933430 | None | DRD Roads Service Car Park | 4 | Downshire Place | ... | None | NaN | NaN | NaN | None | NaN | NaN | None | NaN | NaN |
1 | 553542b8-e033-465c-bdfd-f19b0f7c31b7 | 52b738b303d90a884137546353e09ebb | SC23 | Little Donegall Street, Belfast (Socket | 54.604646 | -5.931866 | None | None | 128 | Little Donegall Street | ... | None | NaN | NaN | NaN | None | NaN | NaN | None | NaN | NaN |
2 | ab00a4df-215b-44ce-a84d-93b1fef6f043 | b58ac8403eb9cf17fae1dcd16df71fde | SC33 | Cromac Street Car Park (Socket 2) | 54.594109 | -5.924292 | None | None | 89-97 | Cromac Street | ... | None | NaN | NaN | NaN | None | NaN | NaN | None | NaN | NaN |
3 | 5e56086e-a688-4c2c-ae2e-7fe732d20123 | fd8c07a31f8a85910ad8476f5f7efb27 | SC03 | Hope Street Car Park, Belfast (Socket 2) | 54.593365 | -5.935574 | None | None | 205 | Sandy Row | ... | None | NaN | NaN | NaN | None | NaN | NaN | None | NaN | NaN |
4 | 9eac95d8-d27d-47f5-85df-2d6de4e5c0c8 | f507783927f2ec2737ba40afbd17efb5 | SC19 | Adelaide Street, Belfast (Socket 2) | 54.594342 | -5.928256 | None | None | 23-91 | Adelaide Street | ... | None | NaN | NaN | NaN | None | NaN | NaN | None | NaN | NaN |
5 rows × 159 columns
print('Shape of data frame ', df.shape)
Shape of data frame (23840, 159)
for column in df.columns:
print(column)
id chargeDeviceID reference name latitude longitude subBuildingName buildingName buildingNumber thoroughfare street doubleDependantLocality dependantLocality town county postcode countryCode uprn deviceDescription locationShortDescription locationLongDescription deviceManufacturer deviceModel deviceOwnerName deviceOwnerWebsite deviceOwnerTelephoneNo deviceOwnerContactName deviceControllerName deviceControllerWebsite deviceControllerTelephoneNo deviceControllerContactName deviceNetworks chargeDeviceStatus publishStatus deviceValidated dateCreated dateUpdated moderated lastUpdated lastUpdatedBy attribution dateDeleted paymentRequired paymentRequiredDetails subscriptionRequired subscriptionRequiredDetails parkingFeesFlag parkingFeesDetails parkingFeesUrl accessRestrictionFlag accessRestrictionDetails physicalRestrictionFlag physicalRestrictionText onStreetFlag locationType bearing access24Hours accessMondayFrom accessMondayTo accessTuesdayFrom accessTuesdayTo accessWednesdayFrom accessWednesdayTo accessThursdayFrom accessThursdayTo accessFridayFrom accessFridayTo accessSaturdayFrom accessSaturdayTo accessSundayFrom accessSundayTo connector1ID connector1Type connector1RatedOutputKW connector1OutputCurrent connector1RatedVoltage connector1ChargeMethod connector1ChargeMode connector1TetheredCable connector1Status connector1Description connector1Validated connector2ID connector2Type connector2RatedOutputKW connector2OutputCurrent connector2RatedVoltage connector2ChargeMethod connector2ChargeMode connector2TetheredCable connector2Status connector2Description connector2Validated connector3ID connector3Type connector3RatedOutputKW connector3OutputCurrent connector3RatedVoltage connector3ChargeMethod connector3ChargeMode connector3TetheredCable connector3Status connector3Description connector3Validated connector4ID connector4Type connector4RatedOutputKW connector4OutputCurrent connector4RatedVoltage connector4ChargeMethod connector4ChargeMode connector4TetheredCable connector4Status connector4Description connector4Validated connector5ID connector5Type connector5RatedOutputKW connector5OutputCurrent connector5RatedVoltage connector5ChargeMethod connector5ChargeMode connector5TetheredCable connector5Status connector5Description connector5Validated connector6ID connector6Type connector6RatedOutputKW connector6OutputCurrent connector6RatedVoltage connector6ChargeMethod connector6ChargeMode connector6TetheredCable connector6Status connector6Description connector6Validated connector7ID connector7Type connector7RatedOutputKW connector7OutputCurrent connector7RatedVoltage connector7ChargeMethod connector7ChargeMode connector7TetheredCable connector7Status connector7Description connector7Validated connector8ID connector8Type connector8RatedOutputKW connector8OutputCurrent connector8RatedVoltage connector8ChargeMethod connector8ChargeMode connector8TetheredCable connector8Status connector8Description connector8Validated
df.describe()
latitude | longitude | deviceOwnerContactName | deviceControllerContactName | deviceValidated | dateDeleted | paymentRequired | subscriptionRequired | parkingFeesFlag | accessRestrictionFlag | ... | connector7Description | connector7Validated | connector8ID | connector8RatedOutputKW | connector8OutputCurrent | connector8RatedVoltage | connector8ChargeMode | connector8TetheredCable | connector8Description | connector8Validated | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 23838.000000 | 23840.000000 | 2.0 | 2.0 | 23840.000000 | 2.0 | 23654.000000 | 23838.000000 | 23838.000000 | 23838.000000 | ... | 0.0 | 14.0 | 12.000000 | 12.000000 | 12.000000 | 12.000000 | 12.000000 | 12.000000 | 0.0 | 12.0 |
mean | 52.229395 | -1.151391 | 3.0 | 0.0 | 0.002685 | 0.0 | 0.385601 | 0.355651 | 0.099631 | 0.027645 | ... | NaN | 0.0 | 164415.000000 | 77.450000 | 168.250000 | 564.166667 | 3.416667 | 0.416667 | NaN | 0.0 |
std | 1.721908 | 1.487102 | 0.0 | 0.0 | 0.293091 | 0.0 | 0.486747 | 0.478720 | 0.299513 | 0.163957 | ... | NaN | 0.0 | 1119.781878 | 86.145411 | 182.596488 | 329.833521 | 0.514929 | 0.514929 | NaN | 0.0 |
min | 0.000000 | -60.953945 | 3.0 | 0.0 | 0.000000 | 0.0 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | NaN | 0.0 | 163613.000000 | 3.700000 | 16.000000 | 230.000000 | 3.000000 | 0.000000 | NaN | 0.0 |
25% | 51.459551 | -1.774669 | 3.0 | 0.0 | 0.000000 | 0.0 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | NaN | 0.0 | 163660.000000 | 7.000000 | 16.000000 | 230.000000 | 3.000000 | 0.000000 | NaN | 0.0 |
50% | 51.536220 | -0.710762 | 3.0 | 0.0 | 0.000000 | 0.0 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | NaN | 0.0 | 163935.000000 | 11.000000 | 32.000000 | 400.000000 | 3.000000 | 0.000000 | NaN | 0.0 |
75% | 52.910899 | -0.178640 | 3.0 | 0.0 | 0.000000 | 0.0 | 1.000000 | 1.000000 | 0.000000 | 0.000000 | ... | NaN | 0.0 | 164432.750000 | 175.000000 | 375.000000 | 930.000000 | 4.000000 | 1.000000 | NaN | 0.0 |
max | 60.759109 | 51.595946 | 3.0 | 0.0 | 32.000000 | 0.0 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | ... | NaN | 0.0 | 166441.000000 | 175.000000 | 375.000000 | 930.000000 | 4.000000 | 1.000000 | NaN | 0.0 |
8 rows × 71 columns
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 23840 entries, 0 to 23839 Columns: 159 entries, id to connector8Validated dtypes: float64(70), int64(1), object(88) memory usage: 28.9+ MB
df.dtypes
id object chargeDeviceID object reference object name object latitude float64 ... connector8ChargeMode float64 connector8TetheredCable float64 connector8Status object connector8Description float64 connector8Validated float64 Length: 159, dtype: object
df.county.value_counts()[:10].plot(kind='bar', color='blue', figsize=(8,6))
# title and labels
plt.title('Top 10 charging stations by county', fontsize=20)
plt.xlabel('County', fontsize=16)
plt.ylabel('Number', fontsize=16)
Text(0, 0.5, 'Number')
The above chart shows the top 10 counties with the most EV charging stations.
df.town.value_counts()[:10].plot(kind='bar', color='blue', figsize=(8,6))
# title and labels
plt.title('Top 10 charging stations by town', fontsize=20)
plt.xlabel('Town', fontsize=16)
plt.ylabel('Number', fontsize=16)
Text(0, 0.5, 'Number')
londonNumber = (df['town'] == 'London').sum()
totalNumber = df.shape[0]
londonPercentage = londonNumber / totalNumber * 100.0
print('Percentage of EV in London:', londonPercentage)
Percentage of EV in London: 20.99832214765101
From the chart above, we discovered London has more than 20% of EV stations in the UK.
df.deviceManufacturer.value_counts()[:10].plot(kind='bar', color='blue', figsize=(8,6))
# title and labels
plt.title('Top 10 Device Manufacturer', fontsize=20)
plt.xlabel('Device Manufacturer', fontsize=16)
plt.ylabel('Number', fontsize=16)
Text(0, 0.5, 'Number')
From the chart above, we can see the top 10 EV charging stations device manufacturer.
df.deviceModel.value_counts()[:10].plot(kind='bar', color='blue', figsize=(8,6))
# title and labels
plt.title('Top 10 Device Model', fontsize=20)
plt.xlabel('Device Model', fontsize=16)
plt.ylabel('Number', fontsize=16)
Text(0, 0.5, 'Number')
The chart above shows the top 10 device model of EV charging stations.
def getPercentage(ax, feature):
total = len(feature)
for p in ax.patches:
percentage = '{:.1f}%'.format(100 * p.get_height()/total)
x = p.get_x() + p.get_width() / 2 - 0.05
y = p.get_y() + p.get_height()
ax.annotate(percentage, (x, y), size = 12)
import seaborn as sns
ax = sns.countplot(x='chargeDeviceStatus', data=df)
getPercentage(ax, df.chargeDeviceStatus)
99.1% of charging stations are in service among all the EV charging stations in the UK.
import seaborn as sns
ax = sns.countplot(x='paymentRequired', data=df)
ax.set_xlabel('Payment Required')
getPercentage(ax, df.paymentRequired)
From the chart above, 61% of the charging stations do not require payment, while the remaining 38.3% of the EV charging stations require payment.
import seaborn as sns
ax = sns.countplot(x='subscriptionRequired', data=df)
ax.set_xlabel('Subscription Required')
getPercentage(ax, df.subscriptionRequired)
64.4% of charging stations do not require a subscription, while the remaining 35.6% of the EV charging stations require a subscription.
import seaborn as sns
ax = sns.countplot(x='access24Hours', data=df)
ax.set_xlabel('24 hours access')
getPercentage(ax, df.access24Hours)
Only 4.3% of charging stations do not provide 24 hours access, while 82.9% of the charging stations are 24 hours.
charging_points = pd.concat([df['connector1RatedOutputKW'], df['connector2RatedOutputKW'], df['connector3RatedOutputKW'],df['connector4RatedOutputKW'],df['connector5RatedOutputKW'],df['connector6RatedOutputKW'],df['connector7RatedOutputKW'],df['connector8RatedOutputKW']])
charging_points = charging_points[charging_points.notnull()]
most_common_power = charging_points.value_counts().head(10)
charging_points.value_counts().head(10).plot(kind='bar', color='blue', figsize=(8,6))
# title and labels
plt.title('Top 10 Common OutputKw', fontsize=20)
plt.xlabel('Output KW', fontsize=16)
plt.ylabel('Count', fontsize=16)
Text(0, 0.5, 'Count')
The above chart shows the standard power outputs of charging stations. We can see that the top 3 most common output KW is 7.0kW, 3.7kW and 22.0kW.
num_charging_points = charging_points.count()
other_power = num_charging_points - most_common_power.sum()
# include other in the most common power output serie
most_common_power.at['other'] = other_power
# define colors of the pie plot
colors = ['darkviolet', 'dodgerblue', 'yellow', 'deeppink', 'orange', 'skyblue', 'salmon', 'green', 'red', 'darkblue', 'springgreen']
# pie plot showing power output of charging points in Germany
most_common_power.plot(kind='pie', figsize=(8, 8), labels=None, colors=colors, fontsize=16)
# legend - percentage of charging points
labels = ['{} kW - {:.2%}'.format(index, most_common_power.loc[index]/num_charging_points) for index in most_common_power.index]
plt.legend(labels, loc='best', bbox_to_anchor=(-0.1, 1.), fontsize=18)
# labels and title
plt.title('Power output of charging points in UK', fontsize=20)
plt.ylabel('')
Text(0, 0.5, '')
ultra_fast_stations = df[(df['connector1RatedOutputKW']>=300.0) | (df['connector2RatedOutputKW']>=300.0) | (df['connector3RatedOutputKW']>=300.0) | (df['connector4RatedOutputKW']>=300.0) | (df['connector5RatedOutputKW']>=300.0) | (df['connector6RatedOutputKW']>=300.0) | (df['connector7RatedOutputKW']>=300.0) | (df['connector8RatedOutputKW']>=300.0)]
print('Number of ultra fast charging stations(>=300kW):', len(ultra_fast_stations))
Number of ultra fast charging stations(>=300kW): 13
import folium
fast_stations = df[(df['connector1RatedOutputKW']>=50.0) | (df['connector2RatedOutputKW']>=50.0) | (df['connector3RatedOutputKW']>=50.0) | (df['connector4RatedOutputKW']>=50.0) | (df['connector5RatedOutputKW']>=50.0) | (df['connector6RatedOutputKW']>=50.0) | (df['connector7RatedOutputKW']>=50.0) | (df['connector8RatedOutputKW']>=50.0)]
normal_stations = df[(df['connector1RatedOutputKW']<50.0) | (df['connector2RatedOutputKW']<50.0) | (df['connector3RatedOutputKW']<50.0) | (df['connector4RatedOutputKW']<50.0) | (df['connector5RatedOutputKW']<50.0) | (df['connector6RatedOutputKW']<50.0) | (df['connector7RatedOutputKW']<50.0) | (df['connector8RatedOutputKW']<50.0)]
mapMarker = folium.Map(location=[51.0260617, -4.3993741], zoom_start=7)
for lat, lng in zip(fast_stations['latitude'], fast_stations['longitude']):
folium.CircleMarker(
[lat, lng],
radius=4,
color='green',
opacity=0.4,
fill=True,
fill_color='green').add_to(mapMarker)
for lat, lng in zip(normal_stations['latitude'], normal_stations['longitude']):
folium.CircleMarker(
[lat, lng],
radius=4,
color='blue',
opacity=0.4,
fill=True,
fill_color='blue').add_to(mapMarker)
def add_categorical_legend(folium_map, title, colors, labels):
if len(colors) != len(labels):
raise ValueError("colors and labels must have the same length.")
color_by_label = dict(zip(labels, colors))
legend_categories = ""
for label, color in color_by_label.items():
legend_categories += f"<li><span style='background:{color}'></span>{label}</li>"
legend_html = f"""
<div id='maplegend' class='maplegend'>
<div class='legend-title'>{title}</div>
<div class='legend-scale'>
<ul class='legend-labels'>
{legend_categories}
</ul>
</div>
</div>
"""
script = f"""
<script type="text/javascript">
var oneTimeExecution = (function() {{
var executed = false;
return function() {{
if (!executed) {{
var checkExist = setInterval(function() {{
if ((document.getElementsByClassName('leaflet-top leaflet-right').length) || (!executed)) {{
document.getElementsByClassName('leaflet-top leaflet-right')[0].style.display = "flex"
document.getElementsByClassName('leaflet-top leaflet-right')[0].style.flexDirection = "column"
document.getElementsByClassName('leaflet-top leaflet-right')[0].innerHTML += `{legend_html}`;
clearInterval(checkExist);
executed = true;
}}
}}, 100);
}}
}};
}})();
oneTimeExecution()
</script>
"""
css = """
<style type='text/css'>
.maplegend {
z-index:9999;
float:right;
background-color: rgba(255, 255, 255, 1);
border-radius: 5px;
border: 2px solid #bbb;
padding: 10px;
font-size:12px;
positon: relative;
}
.maplegend .legend-title {
text-align: left;
margin-bottom: 5px;
font-weight: bold;
font-size: 90%;
}
.maplegend .legend-scale ul {
margin: 0;
margin-bottom: 5px;
padding: 0;
float: left;
list-style: none;
}
.maplegend .legend-scale ul li {
font-size: 80%;
list-style: none;
margin-left: 0;
line-height: 18px;
margin-bottom: 2px;
}
.maplegend ul.legend-labels li span {
display: block;
float: left;
height: 16px;
width: 30px;
margin-right: 5px;
margin-left: 0;
border: 0px solid #ccc;
}
.maplegend .legend-source {
font-size: 80%;
color: #777;
clear: both;
}
.maplegend a {
color: #777;
}
</style>
"""
folium_map.get_root().header.add_child(folium.Element(script + css))
return folium_map
mapMarker = add_categorical_legend(mapMarker, 'UK EV Normal and Fast Charging',
colors = ['green','blue'],
labels = ['Fast EV Charging Stations (>=50kW)', 'Normal EV Charging Stations (<50kW)'])
mapMarker