knitr::opts_chunk$set(warning=F, message=F, results=F, dev='svg')library(tidyverse) #loads multiple packages (see https://tidyverse.tidyverse.org/)#core tidyverse packages loaded:# ggplot2, for data visualisation. https://ggplot2.tidyverse.org/# dplyr, for data manipulation. https://dplyr.tidyverse.org/# tidyr, for data tidying. https://tidyr.tidyverse.org/# readr, for data import. https://readr.tidyverse.org/# purrr, for functional programming. https://purrr.tidyverse.org/# tibble, for tibbles, a modern re-imagining of data frames. https://tibble.tidyverse.org/# stringr, for strings. https://stringr.tidyverse.org/# forcats, for factors. https://forcats.tidyverse.org/# lubridate, for date/times. https://lubridate.tidyverse.org/#also loads the following packages (less frequently used):# Working with specific types of vectors:# hms, for times. https://hms.tidyverse.org/# Importing other types of data:# feather, for sharing with Python and other languages. https://github.com/wesm/feather# haven, for SPSS, SAS and Stata files. https://haven.tidyverse.org/# httr, for web apis. https://httr.r-lib.org/# jsonlite for JSON. https://arxiv.org/abs/1403.2805# readxl, for .xls and .xlsx files. https://readxl.tidyverse.org/# rvest, for web scraping. https://rvest.tidyverse.org/# xml2, for XML. https://xml2.r-lib.org/# Modelling# modelr, for modelling within a pipeline. https://modelr.tidyverse.org/# broom, for turning models into tidy data. https://broom.tidymodels.org/# Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors#loading relevant packageslibrary(networkD3) #for sankeylibrary(htmlwidgets) #for sankeylibrary(readxl) #to read excel filelibrary(cowplot) #for plot_grid, multiple plots#seed for reproducibility of random generationset.seed(123)#setting graphs themetheme_set(theme_minimal() +theme(plot.title =element_text(face="bold") ) )#setting viridis theme for colorsscale_colour_continuous <- scale_colour_viridis_cscale_colour_discrete <- scale_colour_viridis_dscale_colour_binned <- scale_colour_viridis_b#setting viridis theme for fillscale_fill_continuous <- scale_fill_viridis_cscale_fill_discrete <- scale_fill_viridis_dscale_fill_binned <- scale_fill_viridis_bSource <-"Source: \ncomputation by Thomas Starck"# Load the function filesource("functions.R")
Load data
We load the data produced in the preparation pages, for each water agency basin and at the national scale: wastewater treatment plants inflows and outflows, industry discharge to sewers networks, French nutrient ingestion/excretion, share of population with individual autonomous system (not connected to sewers),
Code
#human ingestion/excretionpath_source <-"output_data/nutrient_ingestion_excretion/"#source of the data#load France excretionsfile_human_excretions <-read_csv(paste0(path_source, "excretions_human_france_kt_year.csv")) %>%mutate(basin ="Metropolitan France")#load water agency basins excretions and merge with France excretionsfile_human_excretions <-bind_rows( file_human_excretions,read_csv(paste0(path_source, "excretions_human_basins_kt_year.csv"))) #large industries network discharge at water agency scale and national scalepath_source <-"output_data/industry_sewers_network_discharge/"#source of the datafile_industry_discharge <-read_csv(paste0(path_source, "industry_sewers_network_discharge_GEREP_basins.csv")) #load data #take the mean of the 2015-2020 periodfile_industry_discharge <- file_industry_discharge %>%filter(Year>2014& Year<2021) %>%group_by(basin) %>%summarise(across(c( NGL_in, NGL_out, Pt_in, Pt_out, DBO5_in, DBO5_out, DCO_in, DCO_out, MES_in, MES_out ), ~signif(mean(.x, na.rm=T), 3) ) )#inflow and outflows of WWTP, by basin and at the national scalepath_source <-"output_data/0_final_data/"#source of the datafile_in_out_flows <-read_csv(paste0(path_source, "basins_flows_2015_2020.csv")) #load data#discharged without treatment (% as share of pollution entering WWTP)path_source <-"source_data/10_discharge_without_treatment/"#source of the datafile_direct_discharge <-read_csv(paste0(path_source, "discharge_without_treatment_estimations.csv")) #load data#connection to sewer networkspath_source <-"source_data/pop_sewage_connection/"#source of the datafile_sewage_connection <-read_excel(paste0(path_source, "pop_basins_sewage_connection.xlsx"), range ="A1:F8")#individual autonomous system balancepath_source <-"source_data/pop_sewage_connection/"#source of the datafile_IAS_balance <-read_csv(paste0(path_source, "balance_individual_autonomous_system.csv"))#sludge destinationpath_source <-"output_data/sludge_destination/"file_sludge_destination <-read_csv(paste0(path_source, "sludge_destination.csv"))#N:P ratio of sludgepath_source <-"output_data/sludge_composition/"#source of the datafile_sludge_composition <-read_csv(paste0(path_source, "N_P_sludge_review.csv"))N_P_ratio_sludge_review <- file_sludge_composition$N_P_ratio[file_sludge_composition$compost=="not composted"]#NH3 volatilization during compostingN_compost_volatilization <-0.3#proportion excretions to sewers from people with Individual Autonomous System (due to excretions in public spaces connected to sewers)excre_not_at_house <-0.2
Code
#compute direct discharge at national scaletemp <-left_join(file_in_out_flows, file_direct_discharge, by="basin") %>%select(basin, Pt_in_adj, NGL_in_adj, percent_loss) %>%filter(basin !="Metropolitan France")#compute national percent direct discharge, weighted by basin pollution flowsperc_Pt_loss <-weighted.mean(temp$percent_loss, temp$Pt_in_adj)perc_NGL_loss <-weighted.mean(temp$percent_loss, temp$NGL_in_adj)perc_loss <-round(mean(perc_Pt_loss, perc_NGL_loss), 2)#very similar for N and P, we take the average file_direct_discharge <-rbind( file_direct_discharge, data.frame(basin ="Metropolitan France", percent_loss = perc_loss) )rm(perc_Pt_loss, perc_NGL_loss, perc_loss, temp)
Sankeys P
Code
f_P_file <-function(file_name, basin_selected){#select values of basin human_excretions <- file_human_excretions %>%filter(basin == basin_selected) industry_discharge <- file_industry_discharge %>%filter(basin == basin_selected) %>%select(basin, NGL_in, Pt_in, DBO5_in, DCO_in, MES_in) sewage_connection <- file_sewage_connection %>%filter(basin == basin_selected) in_out_flows <- file_in_out_flows %>%filter(basin == basin_selected) direct_discharge <- file_direct_discharge %>%filter(basin == basin_selected) sludge_destination <- file_sludge_destination %>%filter(basin == basin_selected)#create empty sankey file sankey <-data.frame(source =character(), target =character(),flow_group =character(),value =numeric() )#NETWORK REPARTITION#P excretions to sewage network sankey[nrow(sankey) +1,] =list("excretion", "sewage network", "excretion", human_excretions$P_excretion*( (1-sewage_connection$share_IAS) + sewage_connection$share_IAS*(excre_not_at_house) ) )#P industry to sewage network sankey[nrow(sankey) +1,] =list("large industries", "sewage network", "large industries", industry_discharge$Pt_in )#P from network to WWTP (flow in WWTP) sankey[nrow(sankey) +1,] =list("sewage network", "WWTP", "WWTP", in_out_flows$Pt_in_adj )#P out WWTP in water sankey[nrow(sankey) +1,] =list("WWTP", "water", "water", in_out_flows$Pt_out_adj )#P discharge without treatment sankey[nrow(sankey) +1,] =list("sewage network", "water", "water", in_out_flows$Pt_in_adj*direct_discharge$percent_loss )#P residual sankey[nrow(sankey) +1,] =list("residual", "sewage network", "large industries", sankey$value[sankey$source=="sewage network"& sankey$target=="water"] + sankey$value[sankey$source=="sewage network"& sankey$target=="WWTP"] - sankey$value[sankey$source=="large industries"] - sankey$value[sankey$source=="excretion"& sankey$target=="sewage network"] )#P excretions to IAS sankey[nrow(sankey) +1,] =list("excretion", "IAS", "excretion", human_excretions$P_excretion*sewage_connection$share_IAS*(1-excre_not_at_house) )#residuals P to IAS sankey[nrow(sankey) +1,] =list("residual", "IAS", "sewage network", sankey$value[sankey$source=="residual"& sankey$target=="sewage network"]* sewage_connection$share_IAS/(1-sewage_connection$share_IAS) )#P IAS to water and to WWTP sankey[nrow(sankey) +1,] =list("IAS", "groundwater", "water", IAS_balance$water*( sankey$value[sankey$source=="excretion"& sankey$target=="IAS"] + sankey$value[sankey$source=="residual"& sankey$target=="IAS"] ) ) sankey[nrow(sankey) +1,] =list("IAS", "WWTP", "WWTP", IAS_balance$sludge*( sankey$value[sankey$source=="excretion"& sankey$target=="IAS"] + sankey$value[sankey$source=="residual"& sankey$target=="IAS"] ) )#P in sludge (add also the flow IAS to WWTP for approximation rounding) sankey[nrow(sankey) +1,] =list("WWTP", "sludge", "sludge", in_out_flows$Pt_in_adj-in_out_flows$Pt_out_adj + sankey$value[sankey$source=="IAS"& sankey$target=="WWTP"] )#P in sludge returning to farming system sankey[nrow(sankey) +1,] =list("sludge", "composted, spread", "sludge", sankey$value[sankey$target=="sludge"]*(sludge_destination$spread+sludge_destination$composted) )#P not returning to farming system sankey[nrow(sankey) +1,] =list("sludge", "landfill, incineration..", "lost", sankey$value[sankey$target=="sludge"]-sankey$value[sankey$target=="composted, spread"] )#rounding the results sankey$value <-round(sankey$value, 2)#save filef_save_csv_files( sankey,"output_data/sankey_flows/phosphorus/", file_name )return(sankey)}f_sankey <-function(sankey){# nodes names for the sankey nodes <-data.frame(name=c(as.character(sankey$source), as.character(sankey$target)) %>%unique())# With networkD3, connection must be provided using id, not using real name like in the links dataframe. So we need to add it. sankey$IDsource <-match(sankey$source, nodes$name)-1 sankey$IDtarget <-match(sankey$target, nodes$name)-1# Colors groups (for nodes and flow links) nodes$group <-as.factor(c("nodes_group")) my_color <-'d3.scaleOrdinal() .domain(["excretion","large industries" ,"lost", "water", "sludge", "nodes_group"]) .range(["#7d6608" , "grey" , "#5e5e5e", "#2e86c1", "#6e2c00", "black"])'#to ba able to show decimales on the snkey graph# see here https://stackoverflow.com/questions/72129768/r-networkd3-issues# see also https://stackoverflow.com/questions/74259905/network-d3-sankey-node-value-too-precise-using-htmlwidgetscustomJS <-' function(el,x) { var link = d3.selectAll(".link"); var format = d3.formatLocale({"decimal": ".", "thousands": ",", "grouping": [3], "currency": ["", "\u00a0€"]}).format(",.1f"); link.select("title").select("body") .html(function(d) { return "<pre>" + d.source.name + " \u2192 " + d.target.name + "\\n" + format(d.value) + " ktP" + "<pre>"; }); d3.select(el).selectAll(".node text") .html(function(d) { return d.name + " " + format(d.value) + " ktP"; }); } '# Make the sankey p <-sankeyNetwork(Links = sankey, Nodes = nodes, Source ="IDsource", Target ="IDtarget",Value ="value", NodeID ="name", colourScale=my_color,fontSize=25, units ="ktP", nodePadding =50,sinksRight = T, margin =c(top =0, right =0, bottom =0, left =0),LinkGroup="flow_group", NodeGroup="group" )onRender(p, customJS)}f_table <-function(sankey){# table to see the flow values between nodes knitr::kable( sankey %>%select(source, target, value) %>%mutate(value =round(value, digits =1)), caption ="Values of the flows (ktP)") %>% kableExtra::kable_styling(full_width = F)}
Code
#select IAS balance of P for all sankeysIAS_balance <- file_IAS_balance %>%filter(nutrient=="P")
#function to extract sankey value from source and target nodesf_extract_flow <-function(source_label, target_label, sankey_file){ value_extracted <- sankey_file %>%filter( source == source_label, target == target_label ) %>%pull(value)return(value_extracted)}#function to add 5-95 percentile range to main sankey filef_add_min_max <-function(source_label, target_label, distribution, sankey_file){#add 2.5% percentile sankey_file$min[sankey_file$source==source_label & sankey_file$target==target_label] <-round(quantile(distribution, c(.025, .975)), 1)[1]#add 97.5% percentile sankey_file$max[sankey_file$source==source_label & sankey_file$target==target_label] <-round(quantile(distribution, c(.025, .975)), 1)[2]return(sankey_file)}#number of iterations for monte-carlo simualtion: 1 millionnum_vals <-as.integer(10^6)#for normal distributions (all at 10% uncertainty) we consider that 95% of values are in the +/- 10% interval#so the 2 sigmas interval (95% of values) is +/- 10%, i.e. sigma = 10%/2 of the meanper_SD_normal <-0.1/2#function to generate normal distribution from a sankey value, defined by its source and target nodesf_generate_normal_dstribution <-function(source_label, target_label, sankey_file, print){ sankey_value <-f_extract_flow(source_label, target_label, sankey_file) distr <-rnorm(n = num_vals, mean = sankey_value, sd = sankey_value * per_SD_normal)# if want to plot the distributionif (print==TRUE) {hist(distr) }return(distr)}
P
Code
#reload Sankey P for whole Francesankey_P <-read_csv("output_data/sankey_flows/phosphorus/sankey_P_flows_France.csv")sankey_P$min <-NA#create min column for low range of percentile ditributionsankey_P$max <-NA#create max column for high range of percentile ditribution# P IN WWTP (sewage network to WWTP)# normal distribution 10% uncertaintysource <-"sewage network" ; target <-"WWTP"#source and target of the flowP_in_WWTP <-f_generate_normal_dstribution(source, target, sankey_P, print = F) #create flow distributionsankey_P <-f_add_min_max(source, target, P_in_WWTP, sankey_P) #add min and max percentiles values# P OUT WWTP (WWTP to water), # normal distribution 10% uncertaintysource <-"WWTP" ; target <-"water"#source and target of the flowP_out_WWTP <-f_generate_normal_dstribution(source, target, sankey_P, print = F) #create flow distributionsankey_P <-f_add_min_max(source, target, P_out_WWTP, sankey_P) #add min and max percentiles values# P INDUSTRIES (large industries to sewage network)# normal distribution 10% uncertaintysource <-"large industries" ; target <-"sewage network"#source and target of the flowP_industries <-f_generate_normal_dstribution(source, target, sankey_P, print = F) #create flow distributionsankey_P <-f_add_min_max(source, target, P_industries, sankey_P) #add min and max percentiles values# P EXCRETIONS TO SEWERS AND IAS# excretions. Uncertainty is a uniform distribution based on our value of excretion by per capita (0.44 kgP/year), compared to the range of our literature review (0.44 to 0.58)P_excreted_value <-f_extract_flow("excretion", "sewage network", sankey_P) +f_extract_flow("excretion", "IAS", sankey_P)P_excreted <-runif(n = num_vals, min = P_excreted_value*0.44/0.44, max = P_excreted_value*0.58/0.44)# fraction of the population using individual autonomous systems, uniform distribution 15 to 20% (best estimate 18%)ias_pop <-runif(n = num_vals, min =0.15, max =0.2)# fraction of the excretion from people using IAS that is excreted in public spaces connected to sewers, uniform distribution 5 to 35%frac_from_ias_to_sewers <-runif(n = num_vals, min =0.05, max =0.35)# P EXCRETIONS TO IASP_excr_IAS <- P_excreted*ias_pop*(1-frac_from_ias_to_sewers)sankey_P <-f_add_min_max("excretion", "IAS", P_excr_IAS, sankey_P) #add min and max percentiles values# P EXCRETIONS TO SEWERS P_excr_sewers <- P_excreted - P_excr_IASsankey_P <-f_add_min_max("excretion", "sewage network", P_excr_sewers, sankey_P) #add min and max percentiles values# RESIDUALS TO SEWERS# fraction lost in the sewers before reaching WWTPs, uniform distribution 5 to 20 %frac_sewer_discharge <-runif(n = num_vals, min =0.05, max =0.2) #create flow distribution# incoming P in the sewersP_in_sewers <- P_in_WWTP / (1- frac_sewer_discharge)# first compute residuals coming to sewersP_residual_sewers = P_in_sewers - P_excr_sewers - P_industries #compute residuals#but sometimes residuals to sewers are <0, not possible. Handle this problemmissing_input <- P_residual_sewers <0# checks if negative, i.e. P in sewers < P exr + P industries: this means there cannot be a residualP_in_sewers[missing_input] <- P_excr_sewers[missing_input] + P_industries[missing_input] # if so, update P in sewers, equals to excr + industriesP_residual_sewers[missing_input] =0# and set the residual to zerosankey_P <-f_add_min_max("residual", "sewage network", P_residual_sewers, sankey_P) #add min and max percentiles values# RESIDUALS TO IASP_residual_IAS <- P_residual_sewers * ias_pop / (1- ias_pop) # residual assigned to IASsankey_P <-f_add_min_max("residual", "IAS", P_residual_IAS, sankey_P) #add min and max percentiles values# LOSSES IN SEWERSP_lost_sewers <- P_in_sewers*frac_sewer_dischargesankey_P <-f_add_min_max("sewage network", "water", P_lost_sewers, sankey_P) #add min and max percentiles values# IAS TO GROUNDWATER# share of flow coming to IAS ending in gorundwater (uniform 80-90%)frac_loss_IAS =runif(n=num_vals, min=0.8, max=0.9)# IAS to groundwaterP_loss_IAS <- frac_loss_IAS * (P_residual_IAS + P_excr_IAS)sankey_P <-f_add_min_max("IAS", "groundwater", P_loss_IAS, sankey_P) #add min and max percentiles values# IAS TO WWTP P_IAS_to_WWTP <- P_residual_IAS + P_excr_IAS - P_loss_IASsankey_P <-f_add_min_max("IAS", "WWTP", P_IAS_to_WWTP, sankey_P) #add min and max percentiles values# P SLUDGE (from WWTP to sludge)P_sludge <- P_in_WWTP - P_out_WWTP + P_IAS_to_WWTPsankey_P <-f_add_min_max("WWTP", "sludge", P_sludge, sankey_P) #add min and max percentiles values# P COMPOSTED OR SPREAD# share of sludge that is composted or spread, normal distribution 10% uncertaintyfrac_sluge_recycled_value <-f_extract_flow("sludge", "composted, spread", sankey_P) /f_extract_flow("WWTP", "sludge", sankey_P)frac_sluge_recycled <-rnorm(n=num_vals, mean=frac_sluge_recycled_value, sd=frac_sluge_recycled_value*per_SD_normal)# sludge composted or spreadP_recycled <- frac_sluge_recycled*P_sludgesankey_P <-f_add_min_max("sludge", "composted, spread", P_recycled, sankey_P) #add min and max percentiles values# P LANDFILLED OR BURNTP_landfilled_burnt <- (P_sludge - P_recycled)sankey_P <-f_add_min_max("sludge", "landfill, incineration..", P_landfilled_burnt, sankey_P) #add min and max percentiles values
Code
# now save the Sankey with the min and max valueswrite_csv(sankey_P, "output_data/sankey_flows/phosphorus/sankey_P_flows_France.csv")# Global Assessment# total PP_tot <- (P_excreted + P_residual_sewers + P_residual_IAS + P_industries)round(quantile(P_tot, c(.025, .975)), 1)# removal efficiencyremoval_efficiency <- (1-(P_out_WWTP)/P_in_WWTP)*100round(quantile(removal_efficiency, c(.025, .975)))# IAS and sewers lossesP_IAS_and_sewers <- (P_IAS_to_WWTP + P_lost_sewers)round(quantile(P_IAS_and_sewers, c(.025, .975)), 1)# total residualsP_residuals <- (P_residual_IAS + P_residual_sewers)round(quantile(P_residuals, c(.025, .975)), 1)#OUT# % diffuse losses + surface waterratio_losses <- (P_loss_IAS + P_out_WWTP + P_lost_sewers) / P_tot *100round(quantile(ratio_losses, c(.025, .975)))# % recycledratio_spread <- P_recycled / P_tot *100round(quantile(ratio_spread, c(.025, .975)))# % landfilled or burntratio_landfilled_burnt <- P_landfilled_burnt/P_tot*100round(quantile(ratio_landfilled_burnt, c(.025, .975)))# IN# share residualsratio_residual <- (P_residual_IAS + P_residual_sewers) / P_tot *100round(quantile(ratio_residual, c(.025, .975)))# share excretionratio_excretions <- P_excreted / P_tot *100round(quantile(ratio_excretions, c(.025, .975)))# share industriesratio_industries <- P_industries / P_tot *100round(quantile(ratio_industries, c(.025, .975)))
N
Code
#reload Sankey N for whole Francesankey_N <-read_csv("output_data/sankey_flows/nitrogen/sankey_N_flows_France.csv")sankey_N$min <-NA#create min column for low range of percentile ditributionsankey_N$max <-NA#create max column for high range of percentile ditribution# N IN WWTP (sewage network to WWTP)# normal distribution 10% uncertaintysource <-"sewage network" ; target <-"WWTP"#source and target of the flowN_in_WWTP <-f_generate_normal_dstribution(source, target, sankey_N, print = F) #create flow distributionsankey_N <-f_add_min_max(source, target, N_in_WWTP, sankey_N) #add min and max percentiles values# N OUT WWTP (WWTP to water), # normal distribution 10% uncertaintysource <-"WWTP" ; target <-"water"#source and target of the flowN_out_WWTP <-f_generate_normal_dstribution(source, target, sankey_N, print = F) #create flow distributionsankey_N <-f_add_min_max(source, target, N_out_WWTP, sankey_N) #add min and max percentiles values# N INDUSTRIES (large industries to sewage network)# normal distribution 10% uncertaintysource <-"large industries" ; target <-"sewage network"#source and target of the flowN_industries <-f_generate_normal_dstribution(source, target, sankey_N, print = F) #create flow distributionsankey_N <-f_add_min_max(source, target, N_industries, sankey_N) #add min and max percentiles values# N EXCRETIONS TO SEWERS AND IAS# excretions, normal distribution 10% uncertaintyN_excreted_value <-f_extract_flow("excretion", "sewage network", sankey_N) +f_extract_flow("excretion", "IAS", sankey_N)N_excreted <-rnorm(n = num_vals, mean = N_excreted_value, sd = N_excreted_value * per_SD_normal)# fraction of the population using individual autonomous systems, uniform distribution 15 to 20% (best estimate 18%)ias_pop <-runif(n = num_vals, min =0.15, max =0.2)# fraction of the excretion from people using IAS that is excreted in public spaces connected to sewers, uniform distribution 5 to 35%frac_from_ias_to_sewers <-runif(n = num_vals, min =0.05, max =0.35)# N EXCRETIONS TO IASN_excr_IAS <- N_excreted*ias_pop*(1-frac_from_ias_to_sewers)sankey_N <-f_add_min_max("excretion", "IAS", N_excr_IAS, sankey_N) #add min and max percentiles values# N EXCRETIONS TO SEWERS N_excr_sewers <- N_excreted - N_excr_IASsankey_N <-f_add_min_max("excretion", "sewage network", N_excr_sewers, sankey_N) #add min and max percentiles values# RESIDUALS TO SEWERS# fraction lost in the sewers before reaching WWTPs, uniform distribution 5 to 20 %frac_sewer_discharge <-runif(n = num_vals, min =0.05, max =0.2) #create flow distribution# incoming N in the sewersN_in_sewers <- N_in_WWTP / (1- frac_sewer_discharge)# first compute residuals coming to sewersN_residual_sewers = N_in_sewers - N_excr_sewers - N_industries #compute residuals#but sometimes residuals to sewers are <0, not possible. Handle this problemmissing_input <- N_residual_sewers <0# checks if negative, i.e. P in sewers < P exr + P industries: this means there cannot be a residualN_in_sewers[missing_input] <- N_excr_sewers[missing_input] + N_industries[missing_input] # if so, update P in sewers, equals to excr + industriesN_residual_sewers[missing_input] =0# and set the residual to zerosankey_N <-f_add_min_max("residual", "sewage network", N_residual_sewers, sankey_N) #add min and max percentiles values# RESIDUALS TO IASN_residual_IAS <- N_residual_sewers * ias_pop / (1- ias_pop) # residual assigned to IASsankey_N <-f_add_min_max("residual", "IAS", N_residual_IAS, sankey_N) #add min and max percentiles values# LOSSES IN SEWERSN_lost_sewers <- N_in_sewers*frac_sewer_dischargesankey_N <-f_add_min_max("sewage network", "water", N_lost_sewers, sankey_N) #add min and max percentiles values# IAS TO GROUNDWATER, WWTP AND AIR# share of flow coming to IAS ending in groundwater frac_IAS_groundwater =runif(n=num_vals, min=0.7, max=0.95) # groundwater (uniform 80-90%)frac_IAS_air =runif(n=num_vals, min=0.01, max=0.25) # groundwater (uniform 0-25%)frac_IAS_WWTP =runif(n=num_vals, min=0.045, max =0.055) # sludge, i.e. to WWTP (10% uncertainty on median value of 5%)# IAS to groundwaterN_IAS_groundwater <- frac_IAS_groundwater * (N_residual_IAS + N_excr_IAS)sankey_N <-f_add_min_max("IAS", "groundwater", N_IAS_groundwater, sankey_N) #add min and max percentiles values# IAS TO WWTP N_IAS_to_WWTP <- frac_IAS_WWTP * (N_residual_IAS + N_excr_IAS)sankey_N <-f_add_min_max("IAS", "WWTP", N_IAS_to_WWTP, sankey_N) #add min and max percentiles values# IAS TO AIR N_IAS_to_air <- frac_IAS_air * (N_residual_IAS + N_excr_IAS)sankey_N <-f_add_min_max("IAS", "air", N_IAS_to_air, sankey_N) #add min and max percentiles values# N SLUDGE (from WWTP to sludge)N_P_ratio <-runif(n=num_vals, min =1, max =3) # N:P sludge ration between 1 and 3N_sludge <- N_P_ratio*P_sludgesankey_N <-f_add_min_max("WWTP", "sludge", N_sludge, sankey_N) #add min and max percentiles values# N WWTP TO AIRN_WWTP_to_air <- N_in_WWTP - N_out_WWTP - N_sludge + N_IAS_to_WWTP #add N_IAS_to_WWTP to have consistent mass balance: false but very smallsankey_N <-f_add_min_max("WWTP", "air", N_WWTP_to_air, sankey_N) #add min and max percentiles values# N COMPOSTED, SPREAD, LANDFILLED# share of sludge that is composted, directly spread, or landfilled/burnt. normal distribution 10% uncertaintyfrac_sluge_composted_value <-f_extract_flow("sludge", "composted", sankey_N) /f_extract_flow("WWTP", "sludge", sankey_N)frac_sluge_spread_value <-f_extract_flow("sludge", "spread", sankey_N) /f_extract_flow("WWTP", "sludge", sankey_N)frac_sluge_landfilled_burnt_value <-f_extract_flow("sludge", "landfill, incineration..", sankey_N) /f_extract_flow("WWTP", "sludge", sankey_N)# 10% uncertainties on these fractionsfrac_sluge_composted <-rnorm(n = num_vals, mean = frac_sluge_composted_value, sd = frac_sluge_composted_value * per_SD_normal)frac_sluge_spread <-rnorm(n = num_vals, mean = frac_sluge_spread_value, sd = frac_sluge_spread_value * per_SD_normal)frac_sluge_landfilled_burnt <-rnorm(n = num_vals, mean = frac_sluge_landfilled_burnt_value, sd = frac_sluge_landfilled_burnt_value * per_SD_normal)# N composted, spread, or landfilled/burntN_composted <- frac_sluge_composted*N_sludgeN_spread <- frac_sluge_spread*N_sludgeN_landfilled_burnt <- frac_sluge_landfilled_burnt*N_sludge# add these 3 flows to sankeysankey_N <-f_add_min_max("sludge", "composted", N_composted, sankey_N) #add min and max percentiles valuessankey_N <-f_add_min_max("sludge", "spread", N_spread, sankey_N) #add min and max percentiles valuessankey_N <-f_add_min_max("spread", "recycled", N_spread, sankey_N) #add min and max percentiles valuessankey_N <-f_add_min_max("sludge", "landfill, incineration..", N_landfilled_burnt, sankey_N) #add min and max percentiles values# N VOLATILIZED DURING COMPOSTfrac_volatilized <-runif(n = num_vals, min =0.2, max =0.4)N_compost_air <- N_composted*frac_volatilizedsankey_N <-f_add_min_max("composted", "air", N_compost_air, sankey_N) #add min and max percentiles valuesN_compost_recycled <- N_composted*(1-frac_volatilized)sankey_N <-f_add_min_max("composted", "recycled", N_landfilled_burnt, sankey_N) #add min and max percentiles values
f_sankey <-function(sankey){# nodes names for the sankey nodes <-data.frame(name=c(as.character(sankey$source), as.character(sankey$target)) %>%unique())# With networkD3, connection must be provided using id, not using real name like in the links dataframe. So we need to add it. sankey$IDsource <-match(sankey$source, nodes$name)-1 sankey$IDtarget <-match(sankey$target, nodes$name)-1# Colors groups (for nodes and flow links) nodes$group <-as.factor(c("nodes_group")) my_color <-'d3.scaleOrdinal() .domain(["excretion", "large industries", "residual", "landfill, incineration..", "water", "groundwater", "recycled", "air", "nodes_group"]) .range(["#7d6608", "grey", "grey", "#5e5e5e", "#2e86c1", "#2e86c1", "#6e2c00", "#3ab02a", "black"])'#to ba able to show decimales on the snkey graph# see here https://stackoverflow.com/questions/72129768/r-networkd3-issues# see also https://stackoverflow.com/questions/74259905/network-d3-sankey-node-value-too-precise-using-htmlwidgetscustomJS <-' function(el,x) { var link = d3.selectAll(".link"); var format = d3.formatLocale({"decimal": ".", "thousands": ",", "grouping": [3], "currency": ["", "\u00a0€"]}).format(",.1f"); link.select("title").select("body") .html(function(d) { return "<pre>" + d.source.name + " \u2192 " + d.target.name + "\\n" + format(d.value) + " kgN" + "<pre>"; }); d3.select(el).selectAll(".node text") .html(function(d) { return d.name + " " + format(d.value) + " kgN"; }); } '# Make the sankey p <-sankeyNetwork(Links = sankey, Nodes = nodes, Source ="IDsource", Target ="IDtarget",Value ="value", NodeID ="name", colourScale=my_color,fontSize=25, units ="kgN", nodePadding =50,sinksRight = T, margin =c(top =0, right =0, bottom =0, left =0),LinkGroup="flow_group", NodeGroup="group" )onRender(p, customJS)}
f_sankey <-function(sankey){# nodes names for the sankey nodes <-data.frame(name=c(as.character(sankey$source), as.character(sankey$target)) %>%unique())# With networkD3, connection must be provided using id, not using real name like in the links dataframe. So we need to add it. sankey$IDsource <-match(sankey$source, nodes$name)-1 sankey$IDtarget <-match(sankey$target, nodes$name)-1# Colors groups (for nodes and flow links) nodes$group <-as.factor(c("nodes_group")) my_color <-'d3.scaleOrdinal() .domain(["excretion", "large industries", "residual", "landfill, incineration..", "water", "groundwater", "recycled", "nodes_group"]) .range(["#7d6608", "grey", "grey", "#5e5e5e", "#2e86c1", "#2e86c1", "#6e2c00", "black"])'#to ba able to show decimales on the snkey graph# see here https://stackoverflow.com/questions/72129768/r-networkd3-issues# see also https://stackoverflow.com/questions/74259905/network-d3-sankey-node-value-too-precise-using-htmlwidgetscustomJS <-' function(el,x) { var link = d3.selectAll(".link"); var format = d3.formatLocale({"decimal": ".", "thousands": ",", "grouping": [3], "currency": ["", "\u00a0€"]}).format(",.1f"); link.select("title").select("body") .html(function(d) { return "<pre>" + d.source.name + " \u2192 " + d.target.name + "\\n" + format(d.value) + " gP" + "<pre>"; }); d3.select(el).selectAll(".node text") .html(function(d) { return d.name + " " + format(d.value) + " gP"; }); } '# Make the sankey p <-sankeyNetwork(Links = sankey, Nodes = nodes, Source ="IDsource", Target ="IDtarget",Value ="value", NodeID ="name", colourScale=my_color,fontSize=25, units ="gP", nodePadding =50,sinksRight = T, margin =c(top =0, right =0, bottom =0, left =0),LinkGroup="flow_group", NodeGroup="group" )onRender(p, customJS)}
f_sankey <-function(sankey){# nodes names for the sankey nodes <-data.frame(name=c(as.character(sankey$source), as.character(sankey$target)) %>%unique())# With networkD3, connection must be provided using id, not using real name like in the links dataframe. So we need to add it. sankey$IDsource <-match(sankey$source, nodes$name)-1 sankey$IDtarget <-match(sankey$target, nodes$name)-1# Colors groups (for nodes and flow links) nodes$group <-as.factor(c("nodes_group")) my_color <-'d3.scaleOrdinal() .domain(["excretion", "large industries", "residual", "landfill, incineration..", "water", "groundwater", "recycled", "air", "nodes_group"]) .range(["#7d6608", "grey", "grey", "#5e5e5e", "#2e86c1", "#2e86c1", "#6e2c00", "#3ab02a", "black"])'#to ba able to show decimales on the snkey graph# see here https://stackoverflow.com/questions/72129768/r-networkd3-issues# see also https://stackoverflow.com/questions/74259905/network-d3-sankey-node-value-too-precise-using-htmlwidgetscustomJS <-' function(el,x) { var link = d3.selectAll(".link"); var format = d3.formatLocale({"decimal": ".", "thousands": ",", "grouping": [3], "currency": ["", "\u00a0€"]}).format(",.1f"); link.select("title").select("body") .html(function(d) { return "<pre>" + d.source.name + " \u2192 " + d.target.name + "\\n" + format(d.value) + "%" + "<pre>"; }); d3.select(el).selectAll(".node text") .html(function(d) { return d.name + " " + format(d.value) + "%"; }); } '# Make the sankey p <-sankeyNetwork(Links = sankey, Nodes = nodes, Source ="IDsource", Target ="IDtarget",Value ="value", NodeID ="name", colourScale=my_color,fontSize=25, units ="%", nodePadding =50,sinksRight = T, margin =c(top =0, right =0, bottom =0, left =0),LinkGroup="flow_group", NodeGroup="group" )onRender(p, customJS)}
f_sankey <-function(sankey){# nodes names for the sankey nodes <-data.frame(name=c(as.character(sankey$source), as.character(sankey$target)) %>%unique())# With networkD3, connection must be provided using id, not using real name like in the links dataframe. So we need to add it. sankey$IDsource <-match(sankey$source, nodes$name)-1 sankey$IDtarget <-match(sankey$target, nodes$name)-1# Colors groups (for nodes and flow links) nodes$group <-as.factor(c("nodes_group")) my_color <-'d3.scaleOrdinal() .domain(["excretion", "large industries", "residual", "landfill, incineration..", "water", "groundwater", "recycled", "nodes_group"]) .range(["#7d6608", "grey", "grey", "#5e5e5e", "#2e86c1", "#2e86c1", "#6e2c00", "black"])'#to ba able to show decimales on the snkey graph# see here https://stackoverflow.com/questions/72129768/r-networkd3-issues# see also https://stackoverflow.com/questions/74259905/network-d3-sankey-node-value-too-precise-using-htmlwidgetscustomJS <-' function(el,x) { var link = d3.selectAll(".link"); var format = d3.formatLocale({"decimal": ".", "thousands": ",", "grouping": [3], "currency": ["", "\u00a0€"]}).format(",.1f"); link.select("title").select("body") .html(function(d) { return "<pre>" + d.source.name + " \u2192 " + d.target.name + "\\n" + format(d.value) + "%" + "<pre>"; }); d3.select(el).selectAll(".node text") .html(function(d) { return d.name + " " + format(d.value) + "%"; }); } '# Make the sankey p <-sankeyNetwork(Links = sankey, Nodes = nodes, Source ="IDsource", Target ="IDtarget",Value ="value", NodeID ="name", colourScale=my_color,fontSize=25, units ="%", nodePadding =50,sinksRight = T, margin =c(top =0, right =0, bottom =0, left =0),LinkGroup="flow_group", NodeGroup="group" )onRender(p, customJS)}
To assess our sludge composition, we use the sludge production data for each basin, and take the mean over 2015-2020. We then combine this with the N and P flows computed in the Sankey diagram to deduce the sludge compositions at the basin scales.