Source code of the paper Kotov, E., Goncharov, R., Kulchitsky, Y., Molodtsova, V., Nikitin, B. (2022). Spatial Modelling of Key Regional-Level Factors of COVID-19 Mortality in Russia. GEOGRAPHY, ENVIRONMENT, SUSTAINABILITY, 15(2), 71–83. https://doi.org/10.24057/2071-9388-2021-076
All code is visible for reference. Some custom functions are saved in /R/functions.R.
Intensive socio-economic interactions are a prerequisite for the innovative development of the economy, but at the same time, they may lead to increased epidemiological risks. Persistent migration patterns, the socio-demographic composition of the population, income level, and employment structure by type of economic activity determine the intensity of socio-economic interactions and, therefore, the spread of COVID-19.
We used the excess mortality (mortality from April 2020 to February 2021 compared to the five-year mean) as an indicator of deaths caused directly and indirectly by COVID-19. Similar to some other countries, due to irregularities and discrepancies in the reported infection rates, excess mortality is currently the only available and reliable indicator of the impact of the COVID-19 pandemic in Russia.
We used the regional level data and fit regression models to identify the socio-economic factors that determined the impact of the pandemic. We used ordinary least squares as a baseline model and a selection of spatial models to account for spatial autocorrelation of dependent and independent variables as well as the error terms.
Based on the comparison of AICc (corrected Akaike information criterion) and standard error values, it was found that SEM (spatial error model) is the best option with reliably significant coefficients. Our results show that the most critical factors that increase the excess mortality are the share of the elderly population and the employment structure represented by the share of employees in manufacturing (C economic activity according to European Skills, Competences, and Occupations (ESCO) v1 classification). High humidity as a proxy for temperature and a high number of retail locations per capita reduce the excess mortality. Except for the share of the elderly, most identified factors influence the opportunities and necessities of human interaction and the associated excess mortality.
COVID-19, spatial models, socio-economic factors, climatic factors, excess mortality, Russian regions
The reported study was funded by RFBR according to the research project № 20-04-60490 “Ensuring balanced regional development during a pandemic with spatially differentiated regulation of socio-economic interaction, sectoral composition of the economy and local labour markets”.
# preinstall key packages
if( !"renv" %in% installed.packages()[,'Package'] ) { install.packages("renv") }
if( !"here" %in% installed.packages()[,'Package'] ) { install.packages("here") }
# install other pac
source(here::here("R", "functions.R"))
knitr::opts_chunk$set(cache = FALSE)
knitr::opts_chunk$set(echo = TRUE)
knitr::opts_chunk$set(message = FALSE)
knitr::opts_chunk$set(warning = FALSE)
knitr::opts_chunk$set(dpi = 300)
knitr::opts_chunk$set(fig.width = 10)
knitr::opts_chunk$set(fig.height = 8)
# Set so that long lines in R will be wrapped:
knitr::opts_chunk$set(tidy.opts=list(width.cutoff=60), tidy=TRUE)
invisible(lapply(eval(packages_to_load), require, character.only = TRUE))
Loading region boundaries with attributes and spatial weights matrix.
The matrix of spatial neighbours for the spatial autocorrelation test and the resulting spatial models were created based on region boundary polygons from OpenStreetMap (OpenStreetMap contributors 2017) with GeoDa software (Anselin et. al 2006) using first-order queen contiguity. Regions without neighbours (such as Kaliningrad Region and Sakhalin Region) were manually connected to 2-3 closest regions .
regions_sf <- here::here("data", "region_borders_with_data.gpkg") %>%
st_read(dsn = ., quiet = TRUE)
# simplify geometry for faster map plotting
regions_sf <- st_simplify(regions_sf, dTolerance = 1000)
# calculate population density
regions_sf <- regions_sf %>%
dplyr::mutate(Population_Density = Population/Region_Area_km2)
OKTMO_NUM <- as.integer(regions_sf$OKTMO_NUM)
nb <- here::here("data", "region_borders_with_data.gal") %>%
read.gal(file = ., region.id = OKTMO_NUM)
W <- nb2listw(nb, style = "W")
codebook <- read_excel(here::here("codebook", "codebook.xlsx")) %>%
setDT()
codebook[, `:=`(variable_name_transformed, variable_name), ]
codebook[log_transform_recommended == "yes", `:=`(variable_name_transformed,
paste0(variable_name, "_log")), ]
all_x_vars <- codebook[var_type == "x", variable_name_transformed,
]
Transforming variables that require natural log transformation for better model fit.
Rail Road Density variable is included for reference, but it is not critical for the final model, as it is highly correlated with Road Density. Nonetheless, the transformation of Rail Road Density does require an “increment, then log-transform” approach, as the original data contains 0 values due to missing data in OpenStreetMap.
vars_for_log_transform <- codebook[log_transform_recommended ==
"yes", variable_name, ]
regions_sf_transformed <- regions_sf %>%
mutate_at(vars_for_log_transform, log) %>%
rename_at(vars_for_log_transform, ~paste0(vars_for_log_transform,
"_log")) %>%
mutate(Rail_Road_Density_log = log(regions_sf$Rail_Road_Density +
1))
The following plots demonstrate the top variables that could explain
the Excess mortality
. This illustrates part of the
preliminary exploratory data analysis.
Correlation of Excess Mortality vs all X variables
All variables are either close to normally distributed or have been ln-transformed, so the correlation is reliable in the following table.
The table below is sorted by descending R2 of one-on-one linear
models of Excess mortality
vs corresponding X variable.
cor_table <- correlation_table(regions_sf_transformed)
DT::datatable(cor_table, options = list(scrollX = TRUE, paging = TRUE))
Correlation of Excess Mortality vs top X variables
The plot below illustrates how some of the top variables from the
table above are related to the Excess mortality
as well as
to each other. This illustrates the process of variable selection for
the statistical model.
correlation_pairs_top_x <- yx_vars_correlation_matrix(y_var = "Excess_mortality_apr_feb_per_capita",
x_vars = cor_table[order(-`LM R2`)] %>%
head(7) %>%
.[, `Variable Name`], data_sf = regions_sf_transformed)
correlation_pairs_top_x
We use a standard selection of spatial models. Here is a comparison of different spatial econometric model specifications:
Image source: Vega S.H. and Elhorst J.P. (2015). The Slx Model. Journal of Regional Science, 55(3), 339–363. DOI: 10.1111/jors.12188
The code below demonstrates building the best model and can be changed by the reader to use different variables and see how the results change.
Most variables suffer from spatial autocorrelation.
y <- var_test_spatial(dataset = regions_sf_transformed, target_var_name = "Excess_mortality_apr_feb_per_capita",
weights = W)
x1 <- var_test_spatial(regions_sf_transformed, target_var_name = "Workers_in_C_EconAct_Share",
weights = W)
x2 <- var_test_spatial(regions_sf_transformed, target_var_name = "Post_Working_Age_Population_Share",
weights = W)
three_maps <- (y/x1/x2)
three_maps
Therefore we may expect the OLS regression to perform poorly. However, we will use it as a baseline model.
We first build a series of OLS models to check our hypothesis for the key human interaction variables.
We started with the model M0, which takes into account population density (using population density and residential floor per capita variables), employment in economic sectors that require close human interaction (B – mining, C – manufacturing, G – retail and services, P – education, and in small and medium enterprises in general), and local transportation opportunities and constraints (number of buses and cars per capita).
M0 clearly showed that population density and transportation constraints have no effect on mortality. The only two significant variables are the share of employees in manufacturing (C) and retail & services (G), as these are the only variables with confidence intervals that do not cross the zero-line.
The proximity of the confidence interval to zero may be due to the inclusion of insignificant variables in the model, therefore we removed some of these variables starting with the model M4 below.
M0 <- regions_sf_transformed %>%
lm(data = ., formula = Excess_mortality_apr_feb_per_capita ~
Population_Density_log + Floor_Area_per_capita + Workers_in_C_EconAct_Share +
Workers_in_G_EconAct_Share + Workers_in_P_EconAct_Share +
Workers_in_B_EconAct_Share_log + SME_in_GRDP_Share +
Buses_per_capita_log + Cars_per_capita_x1000 + Urban_Population_Share)
resid_panel(M0)
plot_summs(M0, scale = T, inner_ci_level = 0.9, plot.distributions = F)
M1 is an extension of M0 with climate variables (temperature and humidity). It was found that temperature does not affect mortality, while the effect of humidity is uncertain and should be tried in further models.
M1 <- regions_sf_transformed %>%
lm(data = ., formula = Excess_mortality_apr_feb_per_capita ~
Population_Density_log + Floor_Area_per_capita + Workers_in_C_EconAct_Share +
Workers_in_G_EconAct_Share + Workers_in_P_EconAct_Share +
Workers_in_B_EconAct_Share_log + SME_in_GRDP_Share +
Buses_per_capita_log + Cars_per_capita_x1000 + Jul_Temp_Mean_10yr +
Jul_Humid_Mean_10yr)
resid_panel(M1)
plot_summs(M0, M1, scale = T, inner_ci_level = 0.9, plot.distributions = F,
model.names = paste0("M", 0:1))
M2 adds the healthcare component (number of doctors per capita). It was found that, depending on the model, the confidence interval may touch the zero mark, but this factor is still worth considering in further models.
M2 <- regions_sf_transformed %>%
lm(data = ., formula = Excess_mortality_apr_feb_per_capita ~
Population_Density_log + Floor_Area_per_capita + Workers_in_C_EconAct_Share +
Workers_in_G_EconAct_Share + Workers_in_P_EconAct_Share +
Workers_in_B_EconAct_Share_log + SME_in_GRDP_Share +
Jul_Temp_Mean_10yr + Jul_Humid_Mean_10yr + Doctors_per_capita_x1000)
resid_panel(M2)
plot_summs(M0, M1, M2, scale = T, inner_ci_level = 0.9, plot.distributions = F,
model.names = paste0("M", 0:2))
M3 adds interregional migration flow and opportunity for the spread of COVID-19 following the Hägerstrand’s (1973) spatial diffusion of innovation (via the airport density). It was found that airports have no detectable effect, which suggests that air travel between regions was likely not a significant factor in the COVID-19 spread across Russia, while inter-regional migration is worth considering.
M3 <- regions_sf_transformed %>%
lm(data = ., formula = Excess_mortality_apr_feb_per_capita ~
Population_Density_log + Floor_Area_per_capita + Workers_in_C_EconAct_Share +
Workers_in_G_EconAct_Share + Workers_in_P_EconAct_Share +
Workers_in_B_EconAct_Share_log + SME_in_GRDP_Share +
Jul_Temp_Mean_10yr + Jul_Humid_Mean_10yr + Doctors_per_capita_x1000 +
Migr_Inflow_InterReg_3Y_mean_per_capita_x10000 +
Airport_Density_log)
resid_panel(M3)
plot_summs(M0, M1, M2, M3, scale = T, inner_ci_level = 0.9, plot.distributions = F,
model.names = paste0("M", 0:3))
With the next model M4, we eliminated the non-significant variables from previous models and added age (as the elderly are the most affected by both the virus and the deterioration of regular medical care) and income (following the hypothesis that in poorer regions the population will ignore the restrictions more frequently as they must provide money for their families). It was found that on its own M4 has almost no significant coefficients, however, it provides information on the potential of individual variables.
Population density expressed as residential floor area per capita proved to be insignificant, as its coefficient in M4 falls almost to zero. The coefficients for the share of workers in manufacturing (C) and retail & services (G), the number of doctors per capita, and humidity in M4 and previous models vary slightly but mostly remain significant, suggesting that these two economic domains with intensive and close human interaction are important negative factors of the excess mortality.
M4 <- regions_sf_transformed %>%
lm(data = ., formula = Excess_mortality_apr_feb_per_capita ~
Floor_Area_per_capita + Workers_in_C_EconAct_Share +
Workers_in_G_EconAct_Share + Jul_Humid_Mean_10yr +
Doctors_per_capita_x1000 + Migr_Inflow_InterReg_3Y_mean_per_capita_x10000 +
Airport_Density_log + Post_Working_Age_Population_Share +
Income_per_capita + Population_Below_Living_Wage_Share)
resid_panel(M4)
plot_summs(M0, M1, M2, M3, M4, scale = T, inner_ci_level = 0.9,
plot.distributions = F, model.names = paste0("M", 0:4))
M5 builds on M4 by adding digital skills, the share of e-commerce users and the overall provision of retail businesses. In M5 we can see that income expressed as the share of the population with income below the living wage has a high negative impact on excess mortality. This is counterintuitive but may suggest that those households interacted less, as they had no money to spend.
The higher share of e-commerce users, as well as the higher number of retail locations per capita, also had a negative effect, as the reliance on face-to-face contact was lower in regions with high values for these variables. Interestingly, M5 also suggests that a higher share of the population using government services over the Internet somehow negatively affects mortality.
M5 <- regions_sf_transformed %>%
lm(data = ., formula = Excess_mortality_apr_feb_per_capita ~
Post_Working_Age_Population_Share + Income_per_capita +
Population_Below_Living_Wage_Share + Doctors_per_capita_x1000 +
Salary_Region_to_Country_Ratio + Ecommerce_Users_Share +
Retail_N_per_capita_x1000 + Digital_Gov_Serv_Users_Share)
resid_panel(M5)
plot_summs(M0, M1, M2, M3, M4, M5, scale = T, inner_ci_level = 0.9,
plot.distributions = F, model.names = paste0("M", 0:5))
Finally, models M6_C, M6_G and M6_CG are the ultimate models with the most significant variables that demonstrate a noticeable and explainable effect. The difference is that M6_C uses the share of employees in manufacturing (C), while M6_G replaces it with the share in retail and services (G). M6_CG uses the shares in both C and G economic activities.
Clearly, the share of employees in C and G is almost equally important, both according to the models and the logic behind the variables, however with the M6_C model we are able to capture the effect of retail with the number of retail locations per capita and e-commerce. M6_G and M6_CG, despite their overall similarity to M6_C, do not reproduce the same effects reliably.
M6 models also suggest that the number of doctors is irrelevant, which makes sense compared to the previous models as this variable had a positive effect on excess mortality, which could only be explained by assuming that contacts through doctors were stimulating additional infections.
M6_C <- regions_sf_transformed %>%
lm(data = ., formula = Excess_mortality_apr_feb_per_capita ~
Migr_Inflow_InterReg_3Y_mean_per_capita_x10000 + Doctors_per_capita_x1000 +
Jul_Humid_Mean_10yr + Post_Working_Age_Population_Share +
Retail_N_per_capita_x1000 + Workers_in_C_EconAct_Share +
Ecommerce_Users_Share + Population_Below_Living_Wage_Share)
resid_panel(M6_C)
ols_coefs_up_to_m6c <- plot_summs(M0, M1, M2, M3, M4, M5, M6_C,
scale = T, robust = TRUE, inner_ci_level = 0.9, plot.distributions = F,
model.names = c(paste0("M", 0:5), "M6_C")) + labs(x = "Estimates (z-standardised, robust, CI 95%)")
ols_coefs_up_to_m6c
M6_G <- regions_sf_transformed %>%
lm(data = ., formula = Excess_mortality_apr_feb_per_capita ~
Migr_Inflow_InterReg_3Y_mean_per_capita_x10000 + Doctors_per_capita_x1000 +
Jul_Humid_Mean_10yr + Post_Working_Age_Population_Share +
Retail_N_per_capita_x1000 + Workers_in_G_EconAct_Share +
Ecommerce_Users_Share + Population_Below_Living_Wage_Share)
resid_panel(M6_G)
ols_coefs_m6c_vs_m6_g <- plot_summs(M6_C, M6_G, scale = T, robust = TRUE,
inner_ci_level = 0.9, plot.distributions = F, model.names = c("M6_C",
"M6_G")) + labs(x = "Estimates (z-standardised, robust, CI 95%)")
ols_coefs_m6c_vs_m6_g
M6_CG <- regions_sf_transformed %>%
lm(data = ., formula = Excess_mortality_apr_feb_per_capita ~
Migr_Inflow_InterReg_3Y_mean_per_capita_x10000 + Doctors_per_capita_x1000 +
Jul_Humid_Mean_10yr + Post_Working_Age_Population_Share +
Retail_N_per_capita_x1000 + Workers_in_G_EconAct_Share +
Ecommerce_Users_Share + Workers_in_C_EconAct_Share +
Population_Below_Living_Wage_Share)
resid_panel(M6_CG)
ols_coefs_m6cg <- plot_summs(M6_C, M6_G, M6_CG, scale = T, robust = TRUE,
inner_ci_level = 0.9, plot.distributions = F, model.names = c("M6_C",
"M6_G", "M6_CG")) + labs(x = "Estimates (z-standardised, robust, CI 95%)")
ols_coefs_m6cg
A statistical summary of all OLS models is provided below. It shows that models M6_CG and M6_C are the best according to most model quality metrics. They have the lowest AICc (corrected Akaike information criterion), highest R-squared and adjusted R-squared, and lowest RMSE (root-mean-square error). Therefore, we used these models and their variables as the best baseline for the spatial extension of the model.
MM <- list(M0, M1, M2, M3, M4, M5, M6_C, M6_G, M6_CG)
names(MM) <- c("M0", "M1", "M2", "M3", "M4", "M5", "M6_C", "M6_G",
"M6_CG")
ols_compare_dt <- build_OLS_summaries(model_set = MM, data_sf = regions_sf_transformed)
ols_compare_dt_t <- data.table(Indicator = names(ols_compare_dt)[2:length(ols_compare_dt)],
data.table::transpose(ols_compare_dt, make.names = T))
ols_summaries_plot <- plot_model_comparison(ols_compare_dt)
ols_summaries_plot
We used M6_CG as the baseline OLS model and extended it with spatial specifications. The best models are SEM (Spatial Error Model), LAG (Spatial Lag Model) and OLS. These models have the lowest corrected Akaike Information Criterion (AICc), but the values are very close and not significantly different.
However, SEM helps to compensate for the spatial autocorrelation of some unobserved and unaccounted spatially autocorrelated factors. The LAG model corrects for the spatial autocorrelation of the excess mortality and the fact that the spread of COVID-19 is indeed quite likely to occur between the neighbouring regions, which is also observed on the global scale. Other models (all models below OLS in the figure below) do produce lower model errors, however, they add very little in terms of interpretability of the results in general and the model coefficients.
model_formula_best <- "Excess_mortality_apr_feb_per_capita ~ Migr_IntraReg_3Y_mean_per_capita_x10000 + Doctors_per_capita_x1000 + Jul_Humid_Mean_10yr + Post_Working_Age_Population_Share + Retail_N_per_capita_x1000 + Workers_in_G_EconAct_Share + Workers_in_C_EconAct_Share + Ecommerce_Users_Share + Population_Below_Living_Wage_Share"
regions_sf_transformed_standardised_xs <- standardise_sf(data_sf = regions_sf_transformed,
y_var = "Excess_mortality_apr_feb_per_capita")
model_set_best_spatial <- build_models(data_sf = regions_sf_transformed,
W = W, formula_string = model_formula_best, standardise = FALSE)
model_set_best_spatial_std <- build_models(data_sf = regions_sf_transformed_standardised_xs,
W = W, formula_string = model_formula_best, standardise = FALSE)
model_summaries <- build_model_summaries(model_set_best_spatial,
data_sf = regions_sf_transformed)
model_summaries_std <- build_model_summaries(model_set_best_spatial,
data_sf = regions_sf_transformed_standardised_xs)
model_summaries_best_plot <- plot_model_comparison(model_summaries)
model_summaries_best_plot
spat_coefs <- plot_summs(model_set_best_spatial_std$mod_lm, model_set_best_spatial_std$mod_sem,
model_set_best_spatial_std$mod_lag, model_set_best_spatial_std$mod_sdm,
model_set_best_spatial_std$mod_sdem, model_set_best_spatial_std$mod_slx,
model_set_best_spatial_std$mod_sarar, scale = TRUE, robust = TRUE,
inner_ci_level = 0.9, plot.distributions = F, model.names = c("OLS",
"SEM", "LAG", "SDM", "SDEM", "SLX", "SARAR")) + labs(x = "Estimates (z-standardised, robust, CI 95%, only direct impacts for spatial models)")
spat_coefs
top_workers_vars <- cor_table[order(-`LM R2`)] %>%
head(20) %>%
.[, `Variable Name`] %>%
.[grepl("Workers_in.*", .)]
correlation_pairs_top_x <- yx_vars_correlation_matrix(y_var = "Excess_mortality_apr_feb_per_capita",
x_vars = top_workers_vars, data_sf = regions_sf_transformed)
correlation_pairs_top_x
As we can see from the figure below, shares of employment in many economic activities are highly correlated with each other, as well as with the excess mortality. So hypothetically, a model for excess mortality could be composed completely based on the employment rates.
We have explored this option and can conclude that using just one of the economic activity type G (retail) is the best option. The employment in G does not necessarily capture the whole employment, and therefore interaction, structure, but it is just enough to explain the excess mortality without relying on other economic activities.
workers_correlations <- ggcorr(regions_sf_transformed %>%
st_drop_geometry() %>%
dplyr::select("Excess_mortality_apr_feb_per_capita", "Population_log", contains("Workers_in")) %>%
rename_all(.funs = ~ gsub("Workers_in_|_EconAct_Share|_log", "", .x)) %>%
rename_all(.funs = ~ gsub("Excess_mortality_apr_feb_per_capita", "Excess Mortality", .x)),
nbreaks = 7,
low = "steelblue", mid = "grey90", high = "darkred", hjust = 0.93, size = 6,
label = T, label_color = "grey80", label_size = 3,
# geom = "circle",
layout.exp = 10)
workers_correlations
Let us focus on fewer variables:
workers_correlations_subset <- ggcorr(regions_sf_transformed %>%
st_drop_geometry() %>%
# dplyr::select("Excess_mortality_apr_feb_per_capita", "Population_log",
dplyr::select("Excess_mortality_apr_feb_per_capita",
contains(paste0("Workers_in_", c("A_", "B_", "C_", "D_", "G_", "J_", "K_", "P_", "R_") ))) %>%
rename_all(.funs = ~ gsub("Workers_in_|_EconAct_Share|_log", "", .x)) %>%
rename_all(.funs = ~ gsub("Excess_mortality_apr_feb_per_capita", "Excess Mortality", .x)),
nbreaks = 7,
low = "steelblue", mid = "grey90", high = "darkred", hjust = 0.93, size = 6,
label = T, label_color = "grey80", label_size = 3,
# geom = "circle",
layout.exp = 10)
workers_correlations_subset
Saving the model plots for the paper draft.
ggsave(plot = three_maps, filename = here::here("plots", "fig_01_clusters.tiff"),
device = "tiff", units = "cm", width = 24, height = 20, dpi = 300,
compression = "lzw")
ggsave(plot = three_maps, filename = here::here("plots", "fig_01_clusters.png"),
device = "png", units = "cm", width = 24, height = 20, dpi = 200)
ggsave(plot = ols_coefs_up_to_m6c, filename = here::here("plots",
"fig_02_coefs_ols_up_to_m6c.tiff"), device = "tiff", units = "cm",
width = 20, height = 22, dpi = 140, compression = "lzw")
ggsave(plot = ols_coefs_up_to_m6c, filename = here::here("plots",
"fig_02_coefs_ols_up_to_m6c.png"), device = "png", units = "cm",
width = 20, height = 22, dpi = 140)
ggsave(plot = ols_coefs_m6cg, filename = here::here("plots",
"fig_03_ols_coefs_m6c_vs_m6_g.tiff"), device = "tiff", units = "cm",
width = 20, height = 12, dpi = 140, compression = "lzw")
ggsave(plot = ols_coefs_m6cg, filename = here::here("plots",
"fig_03_ols_coefs_m6c_vs_m6_g.png"), device = "png", units = "cm",
width = 20, height = 12, dpi = 140)
ggsave(plot = spat_coefs, filename = here::here("plots", "fig_06_spat_coefs_compare.tiff"),
device = "tiff", units = "cm", width = 20, height = 22, dpi = 140,
compression = "lzw")
ggsave(plot = spat_coefs, filename = here::here("plots", "fig_06_spat_coefs_compare.png"),
device = "png", units = "cm", width = 20, height = 22, dpi = 140)
ggsave(plot = ols_summaries_plot, filename = here::here("plots",
"fig_04_ols_summaries.tiff"), device = "tiff", units = "cm",
width = 30, height = 20, dpi = 140, compression = "lzw")
ggsave(plot = ols_summaries_plot, filename = here::here("plots",
"fig_04_ols_summaries.png"), device = "png", units = "cm",
width = 30, height = 20, dpi = 140)
ggsave(plot = model_summaries_best_plot, filename = here::here("plots",
"fig_05_spatial_mod_summaries.tiff"), device = "tiff", units = "cm",
width = 30, height = 20, dpi = 140, compression = "lzw")
ggsave(plot = model_summaries_best_plot, filename = here::here("plots",
"fig_05_spatial_mod_summaries.png"), device = "png", units = "cm",
width = 30, height = 20, dpi = 140)
ggsave(plot = workers_correlations, filename = here::here("plots",
"fig_07_empl_correlations.tiff"), device = "tiff", units = "cm",
width = 24, height = 20, dpi = 200, compression = "lzw")
ggsave(plot = workers_correlations, filename = here::here("plots",
"fig_07_empl_correlations.png"), device = "png", units = "cm",
width = 24, height = 20, dpi = 200)