#!/usr/bin/env python # coding: utf-8 # # dotConferences carbon footprint calculator # # We'd love to have your feedback on some of the data or the general process of this calculator! Please write to carbon@dotconferences.com :) # # All emissions are in CO2-equivalent kilograms. # In[25]: CONFERENCE = "dotswift-2019" # dotjs-2018 # ## 1 - Transport # In[4]: # List of different modes of transport and their CO2e emissions per passenger per km TRANSPORTS = { # https://www.oui.sncf/aide/calcul-des-emissions-de-co2-sur-votre-trajet-en-train "train_fr_tgv": { "km": 0.0032 }, "train_fr_ter": { "km": 0.0292 }, "train_fr_eurostar": { "km": 0.0112 }, "train_fr_thalys": { "km": 0.0116 }, "train_fr_ratp": { "km": 0.0038 }, "bus_fr_ouibus": { "km": 0.0228 }, "bus_fr_ratp": { "km": 0.0947 }, "car_fr": { "km": 0.205 }, "plane_fr_national": { "km": 0.168 }, # https://eco-calculateur.dta.aviation-civile.gouv.fr/autres-trajets # TODO # https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/726911/2018_methodology_paper_FINAL_v01-00.pdf # Table 39 "plane_uk_national": { "km": 0.1461 }, "plane_uk_europe": { "km": 0.0895 }, "plane_uk_international": { "km": 0.1041 } } # In[5]: from geopy.geocoders import Nominatim from geopy.distance import geodesic import percache import time cache = percache.Cache("./geocode.cache") geolocator = Nominatim(user_agent="carbon-footprint-estimator") # In[6]: @cache def geocode(location): time.sleep(1) # simple rate limit return geolocator.geocode(location, addressdetails=True) def coords(location): geo = geocode(location) if not geo: return None return (geo.latitude, geo.longitude) def country(location): geo = geocode(location) if not geo: print "*ERROR: could not geocode: %s" % location return "" return geo.raw["address"]["country_code"] def distance(p1, p2): if not p1 or not p2: return 0 d = geodesic(p1, p2) return d.km def footprint_transport(location1, location2, transport="guess"): km = distance(coords(location1), coords(location2)) if km == 0: return 0 if transport == "guess": transport = guess_transport(location1, location2) ghg = TRANSPORTS[transport]["km"] * km return ghg def guess_transport(location1, location2): # Guess the most likely form of transport, with some default assumptions # based on travel to Paris c1 = country(location1) c2 = country(location2) km = distance(coords(location1), coords(location2)) if km == 0 or not c1 or not c2: return "" if {c1, c2} in ({"fr"}, {"fr", "lu"}, {"fr", "ch"}): if km > 100: return "train_fr_tgv" else: return "train_fr_ter" if {c1, c2} == {"fr", "gb"}: if "london" in location1.lower()+location2.lower(): return "train_fr_eurostar" else: return "plane_uk_europe" if {c1, c2} in ({"fr", "be"}, {"fr", "nl"}): return "train_fr_thalys" # International travel if len({c1, c2}) == 2: if km < 3500: return "plane_uk_europe" # TODO plane_fr_europe else: return "plane_uk_international" raise Exception("%s => %s : Not supported" % (location1, location2)) # In[7]: from IPython.display import HTML, display import tabulate def display_table(data): display(HTML(tabulate.tabulate(data, tablefmt='html'))) # In[8]: # Test transports to Paris origins = ["Versailles", "Lille", "Metz", "Bordeaux", "Amsterdam", "London", "Glasgow", "Berlin", "Madrid", "NYC", "Honolulu", "Sydney"] display_table([o, guess_transport(o, "Paris"), footprint_transport(o, "Paris")] for o in origins) # In[15]: from collections import Counter import re origins = [] countries = Counter() cities = Counter() import csv FILE = "%s-attendee-cities.csv" % CONFERENCE # CSV file includes speakers with open(FILE, "r") as f: reader = csv.reader(f, delimiter=',', quotechar='"') for row in reader: if row[1] == "FR" and not row[0]: row[0] = "Paris" row[0] = re.sub("\bcedex\b", "", row[0], flags=re.I) origins.append("%s, %s" % (row[0], row[1])) countries[row[1]] += 1 cities[row[0]] += 1 print("Imported %s attendee origin cities" % len(origins)) print("Top countries:") display_table(countries.most_common(20)) print("Top cities:") display_table(cities.most_common(20)) # In[16]: display_table([repr(o), guess_transport(o, "Paris"), footprint_transport(o, "Paris")] for o in origins) # In[17]: from collections import defaultdict total_footprint_transport_attendees = sum([ footprint_transport(o, "Paris") * 2 # *2 for return trip for o in origins ]) total_footprint_by_transport = defaultdict(float) for o in origins: t = guess_transport(o, "Paris") if t: total_footprint_by_transport[t] += footprint_transport(o, "Paris") * 2 total_distance = sum([ distance(coords(o), coords("Paris")) * 2 for o in origins ]) no_show = 45.0 / len(origins) total_distance *= (1-no_show) total_footprint_transport_attendees *= (1-no_show) print "Total km:", total_distance print "Total CO2e kg footprint:", total_footprint_transport_attendees print "Average km/attendee", total_distance / len(origins) print "Average CO2e kg/attendee:", total_footprint_transport_attendees / len(origins) print "Total CO2e by transport:" for k, v in total_footprint_by_transport.items(): print " %s: %s" % (k, v) # In[18]: from bokeh.io import output_notebook, show from bokeh.plotting import figure from bokeh.models import ( ColumnDataSource, Circle, LogColorMapper, BasicTicker, ColorBar, DataRange1d, PanTool, WheelZoomTool, BoxSelectTool ) from bokeh.models.mappers import ColorMapper, LinearColorMapper from bokeh.palettes import Viridis5 from bokeh.tile_providers import CARTODBPOSITRON_RETINA import json, math import bokeh.tile_providers output_notebook() def coords2mercator(coords): lat, lon = coords r_major = 6378137.000 x = r_major * math.radians(lon) scale = x/lon y = 180.0/math.pi * math.log(math.tan(math.pi/4.0 + lat * (math.pi/180.0)/2.0)) * scale return (x, y) p = figure(x_range=(-16000000, 18000000), y_range=(-4000000, 9000000), x_axis_type="mercator", y_axis_type="mercator", plot_width=900, plot_height=550) p.add_tile(CARTODBPOSITRON_RETINA) unique_coords = Counter() for o in origins: if geocode(o): unique_coords[json.dumps([geocode(o).latitude,geocode(o).longitude])] += 1 source = ColumnDataSource( data=dict( lat=[coords2mercator(json.loads(k))[1] for k, v in unique_coords.most_common()], lon=[coords2mercator(json.loads(k))[0] for k, v in unique_coords.most_common()], x=[(coords2mercator(json.loads(k))[0], coords2mercator(coords("Paris"))[0]) for k, v in unique_coords.most_common()], y=[(coords2mercator(json.loads(k))[1], coords2mercator(coords("Paris"))[1]) for k, v in unique_coords.most_common()], size=[(v*100)**(0.3) for k, v in unique_coords.most_common()], width=[math.sqrt(v) for k, v in unique_coords.most_common()], color=["blue" for k, v in unique_coords.most_common()] ) ) #lines_glyph = p.multi_line('x', 'y', color = 'color', line_width = "width", # line_alpha = 0.2, hover_line_alpha = 1.0, hover_line_color = 'color', # source = source) p.circle(x="lon", y="lat", size="size", fill_color="color", fill_alpha=0.5, line_color=None, source=source) show(p) # In[19]: # Commute to the conference # Compute subway emissions, considering ~90% usage among attendees to get to the conference split_subway = 0.90 split_car = 0.10 average_distance = 7 # Distance from Chatelet to Docks subway_co2ekm = 0.0038 total_footprint_subway = len(origins) * 2 * split_subway * average_distance * subway_co2ekm print "Total subway footprint:", total_footprint_subway average_distance = 7 # Distance from Chatelet to Docks subway_co2ekm = 0.205 total_footprint_car = len(origins) * 2 * split_car * average_distance * subway_co2ekm print "Total car footprint:", total_footprint_car total_footprint_commute = total_footprint_subway + total_footprint_car print "Total commute footprint:", total_footprint_commute # In[20]: # Other transports # Food # Deliveries total_footprint_transport = total_footprint_transport_attendees + total_footprint_commute print "Total transport footprint:", total_footprint_transport # ## 2 - Hotels # In[21]: # https://www.consoglobe.com/impact-ecologique-d-une-nuit-d-hotel-cg one_night_ghg = 6.9 average_stay = 2 # Any attendee with an origin further than this (in km) will be considered as sleeping in a hotel hotel_km_limit = 100 hotel_attendees = len([ o for o in origins if distance(coords(o), coords("Paris")) > hotel_km_limit ]) total_footprint_hotels = hotel_attendees * average_stay * one_night_ghg print "Total hotel nights:", average_stay * hotel_attendees print "Total hotel footprint:", total_footprint_hotels # ## 3 - Energy # In[22]: docks_surface = 3200 # Watts estimate heating_kwh = 474 * 24 # https://www.rte-france.com/en/eco2mix/eco2mix-co2-en kwh_co2e = 0.08 total_heating = heating_kwh * kwh_co2e # lights, screen, tech total_other_energy = 0.5 * total_heating # TODO total_footprint_energy = total_heating + total_other_energy print "Total energy footprint:", total_footprint_energy # ## 4 - Food # In[29]: attendees = len(origins) if CONFERENCE == "dotswift-2019": # half-day kg_per_attendee = 0.2 else: kg_per_attendee = 0.5 # http://www.greeneatz.com/foods-carbon-footprint.html # TODO: find better FR source cheese_ghg = 13.5 # Let's just consider everyone eats only cheese! (among the worst offenders) total_footprint_food = attendees * kg_per_attendee * cheese_ghg print "Total food footprint", total_footprint_food # ## 5 - Hardware # In[26]: # Badges & print badges_paper = 0.3 badges_format = 0.075 * 0.120 rollups_surface = 12 * (0.8 * 2) + 6 * (1.6 * 2) + 5 * (2 * 2) rollups_paper = 0.5 total_paper_kg = (badges_paper * badges_format * len(origins)) + (rollups_surface * rollups_paper) # https://www.epa.vic.gov.au/~/media/Publications/972.pdf total_footprint_paper = 2.727 * total_paper_kg # Stage total_stage = 0 total_swag = 0 if CONFERENCE == "dotjs-2018": stage_wood_kg = 4 * 15 stage_cardboard_kg = 26 * 0.5 total_stage = stage_wood_kg + stage_cardboard_kg # Swag tshirts = 100 + 450 # https://www.carbontrust.com/media/38358/ctc793-international-carbon-flows-clothing.pdf total_footprint_tshirts = tshirts * 15 total_swag = total_footprint_tshirts hoodies = 0 # Cardboard total_footprint_hardware = total_footprint_paper + total_stage + total_swag print "Total hardware footprint:", total_footprint_hardware # ## Conclusion # In[30]: total_footprint = total_footprint_transport + total_footprint_hotels + total_footprint_energy + total_footprint_food + total_footprint_hardware print "Total footprint:", total_footprint print "Footprint per attendee", total_footprint / len(origins) # In[31]: from bokeh.palettes import Category10 from bokeh.transform import cumsum import pandas as pd from math import pi raw = { 'Transport': total_footprint_transport, 'Hotels': total_footprint_hotels, 'Energy': total_footprint_energy, 'Food': total_footprint_food, 'Hardware': total_footprint_hardware } data = pd.Series(raw).reset_index(name='value').rename(columns={'index':'label'}) data['angle'] = data['value']/data['value'].sum() * 2*pi data['color'] = Category10[len(raw)] p = figure(plot_height=350, title="Footprint by category", toolbar_location=None, tools="hover", tooltips="@label: @value", x_range=(-0.5, 1.0)) p.wedge(x=0, y=1, radius=0.4, start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), line_color="white", fill_color='color', legend='label', source=dict(data)) p.axis.axis_label=None p.axis.visible=False p.grid.grid_line_color = None show(p) print raw # In[ ]: # In[ ]: # In[ ]: