Below I’ve (somewhat arbitrarily) split the provinces in to larger regional groups and plotted the average monthly rainfall by province and month. As Hirat, is our initial Area of Interest (AOI), I’ve highlighted it in the plot.
Code
df_proc |>group_by(grp,adm1_name, mo =month(date,label =T, abbr=T)) |>summarise(rainfall =mean(rainfall,na.rm=T) ) |>mutate(prov_label =case_when( adm1_name =="Hirat"~adm1_name,TRUE~"other" ),grp_label =case_when(str_detect(grp,"ne")~"North East",str_detect(grp,"nw")~"North West",str_detect(grp,"sw")~"South West",str_detect(grp,"central")~"Central" ) ) |>ggplot(aes(x=mo, y=rainfall, group=adm1_name, color = prov_label) )+geom_line(aes(alpha= prov_label, color = prov_label))+scale_alpha_manual(values=c(1,0.1))+scale_color_manual(values=c("red","black"))+facet_wrap(~grp_label)+labs(title="Monthly Average Rainfall By Province",subtitle="Afghanistan",y="rainfall (mm)")
Let’s focus on Hirat to simplify the research questions and visuals.
Below we look at absolute rainfall anomaly and ONI. When we look at all months no clear relationships pop out.
Code
p_scatter_hirat <- df_hirat |>ggplot(aes(x= anom, y= rainfall_abs_anom) )+geom_point(alpha=0.5)+labs(x ="ENSO ONI Anomaly",y="Rainfall Anomaly (mm)",title ="Monthly ONI anomaly and rainfall not strongly correlated",subtitle="Hirat Afghanistan",caption ="Average total monthly rainfall anomaly across Hirat province againt global monthly ONI anomaly (1981-2024)" )+scale_y_continuous()p_boxplot_hirat <- df_hirat |>ggplot(aes(x= enso_class, y= rainfall_abs_anom) )+geom_boxplot()+labs(y="Rainfall Anomaly (mm)",title ="Monthly rainfall anomaly not well differentiated by ENSO class",subtitle="Hirat Afghanistan",caption ="Average total monthly rainfall anomaly by ONI scores classified an ONI score of: - ≥ 0.5 -> El Nino - <0.5 to -0.5 -> Neutral - ≤ -0.5 -> La Nina" )+scale_y_continuous()+theme(axis.text.x=element_text(size=8,angle=0),axis.title.x =element_blank() )
Code
p_scatter_hirat + p_boxplot_hirat
Winter wheat growing season
Perhaps different seasons/months have different relationships (stronger/less strong) and this is adding noise.
A this stage of the project I believe the main season of interest is the Winter Wheat Season.
FAO Crop Calendar shows that this growing season is Dec-April (inclusive). Therefore let’s filter down to these months.
Code
# let's just copy the code above and filter by to the winter wheat growing season and change plot titledf_hirat_ww <- df_hirat |>filter(month(date)%in%months_of_interest()$winter_wheat)p_scatter_hirat_ww <- df_hirat_ww |>ggplot(aes(x= anom, y= rainfall_abs_anom) )+geom_point(alpha=0.5)+labs(x ="ENSO ONI Anomaly",y="Rainfall Anomaly (mm)",title ="Monthly ONI anomaly and rainfall not strongly correlated",subtitle="Hirat Afghanistan - Winter Wheat Growing Season",caption ="Average total monthly rainfall anomaly across Hirat province againt global monthly ONI anomaly (1981-2024)" )+scale_y_continuous()p_boxplot_hirat_ww <- df_hirat_ww |>ggplot(aes(x= enso_class, y= rainfall_abs_anom) )+geom_boxplot()+labs(y="Rainfall Anomaly (mm)",title ="Drier conditions observable in La Nina phase",subtitle="Hirat Afghanistan - Winter Wheat Growing Season",caption ="Average total monthly rainfall anomaly by ONI scores classified an ONI score of: - ≥ 0.5 -> El Nino - <0.5 to -0.5 -> Neutral - ≤ -0.5 -> La Nina" )+scale_y_continuous()+theme(axis.title.x=element_blank(),axis.text.x =element_text(angle=0) )
Plots are still relatively similar after filtering
Code
p_scatter_hirat_ww + p_boxplot_hirat_ww
Instead of looking at monthly values lets’ look at the whole season as one value (anomaly and rainfall)
So we take the average ONI and sum of rainfall for all seasons Dec-April
Here we do see a bit of relationship and the lowest rainfall anomalies do tend to have lower ENSO ONI values. However, there are lots of exceptions where we see strongly negative ONI values assocaited with strongly positive anomalies as will as strongly positive ONI values with low/negative rainfall values
pals <-proj_palettes()df_proc_seasonal_summary |>filter(adm1_name =="Hirat") |>ggplot(aes(x= mean_oni, y= rainfall_abs_anom, color=enso_class) )+geom_point(alpha=1)+scale_color_manual(values = pals$enso_palette)+labs(x ="ENSO ONI Anomaly",y="Rainfall Anomaly (mm)",title ="Monthly ONI anomaly and rainfall not strongly correlated",subtitle="Hirat Afghanistan - Winter Wheat Growing Season",caption ="Average rainfall anomaly across Hirat winter wheat growing season (Dec-April) plotted against global ONI anomaly averaged for each season (1981-2024)" )+scale_y_continuous()
This might be the take away plot. We do see drier conditions over that season most clearly here, but as seen in the rest of the analysis the relationship is messy:
Okay so let’s say make the assumptions that there is weak but significant relationship, how can we use this going forward and what else do we need to test
NDVI & ONI
Method 1 (WFP NDVI)
Rather than processing and extracting NDVI from MODIS/GEE I was trying to use WFP NDVI values on HDX - values are at admin 2 level. The only admin id is adm2_pcodes
to summaries to adm1 level, we would need to join to spatial admin 2 layer and then do weighted summaries based on geometry area. Currently waiting for a suitable admin 2 boundary file. In the meantime we explore admin 2 level data inside Hirat province
We summaries the dekadal WFP data to month via average and compare with global ONI.
Results Method 1 (WFP NDVI)
Here we look at all historical months for which we have both NDVI & ONI (2002-2024)
correlation generally weak, although in some provinces we might see a weak, but significant positive correlation between ONI and NDVI.
Next we filter the values to only monthly values within winter wheat growing season (Dec -April)
similar results, perhaps some positive correlations are easier to see?
Code
df_hirat_ndvi_oni_ww <- df_hirat_ndvi_oni |>filter( date_yr_mo<"2024-04-01",month(date_yr_mo) %in%months_of_interest()$winter_wheat )df_hirat_ndvi_oni_ww |>ggplot(aes(x= anom, y= viq, color = enso_class) )+geom_point(alpha=0.4, size=1)+scale_color_manual(values = pals$enso_palette)+facet_wrap(~adm2_en,scales="free")+labs(title="Monthly ONI vs NDVI Anomaly by Admin 2",subtitle ="Afghanistan - Hirat Province - Winter Wheat Growing Season Months" )
Next instead of looking at individual months we take the average of the values across each winter wheat season. This simplifies the plot quite a bit and some positive correlations are easier to see?
Code
df_hirat_ww_seas <- df_hirat_ndvi_oni_ww |>group_by(across(matches("adm\\d")) ) |>mutate(mean_oni = zoo::rollmeanr(anom,4,fill=NA),viq = zoo::rollmean(viq,4,fill=NA), ) |>filter(month(date_yr_mo)==4 ) |>group_by(across(matches("adm\\d")) ) |>mutate(enso_class=oni_to_enso_class(mean_oni) ) |>ungroup()df_hirat_ww_seas |>ggplot(aes(x= mean_oni, y= viq, color = enso_class) )+geom_point(alpha=1, size=1)+scale_color_manual(values = pals$enso_palette)+facet_wrap(~adm2_en,scales="free")+labs(x ="ENSO ONI Anomaly (Seasonal)",y="NDVI Anomaly (Seasonal)",title="Seasonal ONI vs NDVI Anomaly by Admin 2",subtitle ="Afghanistan - Hirat Province - Winter Wheat Growing Season Aggregated" )
Discussion Method 1 (WFP NDVI)
positive direction is what we expect with less health vegetation being associated with “drier” la nina conditions.
Qualitatively this looks to be the case in a number of districts in Hirat province but not all
Need better understanding of differences at admin 2 level w/ respect to land use
Limitaiton Method 1 (WFP NDVI)
analysis is very limited due to rigidity of data structure for a number of reasons
As data is already in tabular format cannot apply crop or active crop masks
Issue with spatial CODs that can be harmonized which would allow analysis-level flexibility
Recommendation:
run analysis with MODIS data with crop masks/active crop area masks at admin 1 level
Below are the admin 1 level results. Here we plot ONI vs NDVI. Both values are averaged over the winter wheat growing season.
Code
df_proc_seasonal_summary |>filter(!is.na(ndvi) ) |>ggplot(aes(x= mean_oni, y= ndvi_abs_anom,color = enso_class) )+geom_point(alpha=1, size=1)+scale_color_manual(values = pals$enso_palette)+facet_wrap(~adm1_name,scales="free")+labs(x="ONI",y="NDVI anomaly",title="ONI vs NDVI over cropland by district",subtitle ="Summarised over winter wheat growing season" )
Here we look the NDVI anomaly (winter wheat season) over time for each province
Code
df_proc_seasonal_summary |>filter(!is.na(ndvi) ) |>mutate(adm1_color=if_else(adm1_name=="Hirat",adm1_name,"other") ) |>ggplot(aes(x= date, y= ndvi_abs_anom,group=adm1_name, color=adm1_color, alpha=adm1_color) )+geom_line()+scale_alpha_manual(values=c(1,0.1))+scale_color_manual(values=c("red","black"))+labs(title ="NDVI anomaly over cropland",subtitle ="Each line is a province, Hirat is in red",y="NDVI anomaly (absolute)" )+theme(axis.title.x =element_blank(),legend.position ="none" )
Code
df_proc_seasonal_summary |>filter(!is.na(ndvi), adm1_name=="Hirat" ) |>ggplot(aes(x= mean_oni, y= ndvi_abs_anom,color = enso_class) )+geom_point(alpha=1, size=1)+geom_text(aes(label =year(date)))+scale_color_manual(values = pals$enso_palette)+facet_wrap(~adm1_name,scales="free")+labs(x="ONI",y="NDVI anomaly",title="ONI vs NDVI over cropland by district",subtitle ="Summarised over winter wheat growing season" )
Snow Cover
Just a quick look at MODIS NDSI snow cover data. Unclear exactly what we will do next, but this is just exploratory to see how feasible useful it might be:
Zonal means (per adm 1) calculated per admin 1 for each temporal composite band. Therefore we have a monthly average value per admin for all months 2000-current for each temporal composite (min, max, mean). So we have the average minimum snowfall, average maximum snowfall, average mean snowfall composite values as tabular.
Below we just perform a gut-check to see how CHIRPS precipitation lines up with snowfall.
when filtered to winter months where it would make sense for rainfall + snow to be correlated it does!
thought that perhaps rainfall lagged by 1 month might be correlated w/ snow cover, but relationship is weaker
Code
df_proc |>filter(month(date) %in%months_of_interest()$winter_wheat, adm1_name =="Hirat" ) |>ggplot(aes(x= rainfall, y= snow_frac) )+geom_point(alpha=0.7)+labs(x="CHIRPS Rainfall (mm)",y="MODIS - NDSI Snow Cover Fraction",title ="Rainfall (CHIRPS) vs Snow Frac (MODIS NDSI)",subtitle="Hirat Afghanistan - November to February",caption ="Correlation between monthly average NDSI and CHIRPS average rainfall" )
Code
df_proc_seasonal_summary |>filter( adm1_name =="Hirat" ) |>ggplot(aes(x= rainfall, y= snow_frac) )+geom_point(alpha=0.7)+labs(x="CHIRPS Rainfall (mm)",y="MODIS - NDSI Snow Cover Fraction",title ="Rainfall (CHIRPS) vs Snow Frac (MODIS NDSI)",subtitle="Hirat Afghanistan - Aggregated for entire season (D-A)",caption ="Correlation between monthly average NDSI and CHIRPS average rainfall" )
Code
df_proc |>filter(month(date) %in%months_of_interest()$winter_wheat, adm1_name =="Hirat" ) |>mutate(chirps_lag1 =lag(rainfall,1) ) |>ggplot(aes(x= chirps_lag1, y= snow_frac) )+geom_point(alpha=0.7)+labs(x="CHIRPS Rainfall Lag 1 month (mm)",y="MODIS - NDSI Snow Cover Fraction",title ="Rainfall Lagged 1 month (CHIRPS) vs Snow Frac (MODIS NDSI)",subtitle="Hirat Afghanistan - November to February",caption ="Less correlation between monthly average NDSI and 1 month lagged CHIRPS rainfall" )
Code
df_proc_seasonal_summary |>filter( adm1_name =="Hirat" ) |>ggplot(aes(x= mean_oni, y= snow_frac_abs_anom, color=enso_class) )+geom_point(alpha=1)+ ggrepel::geom_text_repel(aes(label =format(date,"%y")))+# geom_text(aes(label = format(date,"%y")),nudge_x=0.1,nudge_y = 0.5)+scale_color_manual(values = pals$enso_palette)+labs(x ="ENSO ONI Anomaly",y="Snow Frac Anomaly",title ="Correlation between ONI anomaly and average snow fraction anomaly",subtitle="Hirat Afghanistan - Winter Wheat Growing Season",caption ="Average snow frac anomaly across Hirat winter wheat growing season (Dec-April) plotted against global ONI anomaly averaged for each season (2000-2024). Could be a good correlation if high leverage outliers removed?" )+scale_y_continuous()