copro 0.1.2__tar.gz → 2.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {copro-0.1.2 → copro-2.0.2}/PKG-INFO +34 -13
- {copro-0.1.2 → copro-2.0.2}/README.rst +11 -11
- copro-2.0.2/copro/__init__.py +4 -0
- copro-2.0.2/copro/conflict.py +308 -0
- copro-2.0.2/copro/evaluation.py +151 -0
- copro-2.0.2/copro/io.py +107 -0
- copro-2.0.2/copro/machine_learning.py +344 -0
- copro-2.0.2/copro/models.py +372 -0
- copro-2.0.2/copro/nb.py +64 -0
- {copro-0.1.2 → copro-2.0.2}/copro/plots.py +36 -55
- copro-2.0.2/copro/scripts/copro_runner.py +115 -0
- copro-2.0.2/copro/selection.py +146 -0
- copro-2.0.2/copro/settings.py +201 -0
- copro-2.0.2/copro/utils.py +147 -0
- copro-2.0.2/copro/variables.py +209 -0
- copro-2.0.2/copro/xydata.py +457 -0
- {copro-0.1.2 → copro-2.0.2}/copro.egg-info/PKG-INFO +34 -13
- {copro-0.1.2 → copro-2.0.2}/copro.egg-info/SOURCES.txt +5 -5
- copro-2.0.2/copro.egg-info/requires.txt +10 -0
- {copro-0.1.2 → copro-2.0.2}/setup.cfg +1 -1
- copro-2.0.2/setup.py +65 -0
- {copro-0.1.2 → copro-2.0.2}/tests/test_conflict.py +7 -16
- copro-2.0.2/tests/test_data.py +15 -0
- copro-0.1.2/copro/__init__.py +0 -16
- copro-0.1.2/copro/conflict.py +0 -318
- copro-0.1.2/copro/data.py +0 -364
- copro-0.1.2/copro/evaluation.py +0 -280
- copro-0.1.2/copro/machine_learning.py +0 -191
- copro-0.1.2/copro/models.py +0 -200
- copro-0.1.2/copro/pipeline.py +0 -228
- copro-0.1.2/copro/scripts/copro_runner.py +0 -137
- copro-0.1.2/copro/selection.py +0 -191
- copro-0.1.2/copro/utils.py +0 -419
- copro-0.1.2/copro/variables.py +0 -226
- copro-0.1.2/copro.egg-info/requires.txt +0 -10
- copro-0.1.2/setup.py +0 -58
- copro-0.1.2/tests/test_data.py +0 -31
- copro-0.1.2/tests/test_machine_learning.py +0 -33
- copro-0.1.2/tests/test_utils.py +0 -26
- {copro-0.1.2 → copro-2.0.2}/LICENSE +0 -0
- {copro-0.1.2 → copro-2.0.2}/copro/scripts/__init__.py +0 -0
- {copro-0.1.2 → copro-2.0.2}/copro.egg-info/dependency_links.txt +0 -0
- {copro-0.1.2 → copro-2.0.2}/copro.egg-info/entry_points.txt +0 -0
- {copro-0.1.2 → copro-2.0.2}/copro.egg-info/not-zip-safe +0 -0
- {copro-0.1.2 → copro-2.0.2}/copro.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: copro
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Python-model build on scikit-learn functions, designed to facilitate the set-up, execution, and evaluation of machine-learning models for the study of the climate-conflict nexus.
|
|
5
5
|
Home-page: https://copro.readthedocs.io/
|
|
6
6
|
Author: Jannis M. Hoch
|
|
@@ -16,6 +16,27 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Requires-Python: >=3.9
|
|
18
18
|
License-File: LICENSE
|
|
19
|
+
Requires-Dist: click==8.1.7
|
|
20
|
+
Requires-Dist: xarray==2023.12.0
|
|
21
|
+
Requires-Dist: pandas==2.1.4
|
|
22
|
+
Requires-Dist: rasterio==1.2.10
|
|
23
|
+
Requires-Dist: rioxarray==0.15.0
|
|
24
|
+
Requires-Dist: rasterstats==0.19.0
|
|
25
|
+
Requires-Dist: geopandas==1.1.2
|
|
26
|
+
Requires-Dist: numpy==1.26.3
|
|
27
|
+
Requires-Dist: scikit-learn==1.5.0
|
|
28
|
+
Requires-Dist: netcdf4==1.6.0
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: author-email
|
|
31
|
+
Dynamic: classifier
|
|
32
|
+
Dynamic: description
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: keywords
|
|
35
|
+
Dynamic: license
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
Dynamic: requires-dist
|
|
38
|
+
Dynamic: requires-python
|
|
39
|
+
Dynamic: summary
|
|
19
40
|
|
|
20
41
|
===============
|
|
21
42
|
CoPro
|
|
@@ -23,9 +44,6 @@ CoPro
|
|
|
23
44
|
|
|
24
45
|
Welcome to CoPro, a machine-learning tool for conflict risk projections based on climate, environmental, and societal drivers.
|
|
25
46
|
|
|
26
|
-
.. image:: https://travis-ci.com/JannisHoch/copro.svg?branch=dev
|
|
27
|
-
:target: https://travis-ci.com/JannisHoch/copro
|
|
28
|
-
|
|
29
47
|
.. image:: https://img.shields.io/badge/License-MIT-blue.svg
|
|
30
48
|
:target: https://github.com/JannisHoch/copro/blob/dev/LICENSE
|
|
31
49
|
|
|
@@ -33,10 +51,10 @@ Welcome to CoPro, a machine-learning tool for conflict risk projections based on
|
|
|
33
51
|
:target: https://copro.readthedocs.io/en/latest/?badge=latest
|
|
34
52
|
|
|
35
53
|
.. image:: https://img.shields.io/github/v/release/JannisHoch/copro
|
|
36
|
-
:target: https://github.com/JannisHoch/copro/releases/tag/
|
|
54
|
+
:target: https://github.com/JannisHoch/copro/releases/tag/v2.0.1
|
|
37
55
|
|
|
38
56
|
.. image:: https://zenodo.org/badge/254407279.svg
|
|
39
|
-
:target: https://
|
|
57
|
+
:target: https://doi.org/10.5281/zenodo.4061705
|
|
40
58
|
|
|
41
59
|
.. image:: https://badges.frapsoft.com/os/v2/open-source.svg?v=103
|
|
42
60
|
:target: https://github.com/ellerbrock/open-source-badges/
|
|
@@ -44,8 +62,8 @@ Welcome to CoPro, a machine-learning tool for conflict risk projections based on
|
|
|
44
62
|
.. image:: https://joss.theoj.org/papers/1f03334e56413ff71f65092ecc609aa4/status.svg
|
|
45
63
|
:target: https://joss.theoj.org/papers/1f03334e56413ff71f65092ecc609aa4
|
|
46
64
|
|
|
47
|
-
.. image:: https://mybinder.org/badge_logo.svg
|
|
48
|
-
|
|
65
|
+
.. .. image:: https://mybinder.org/badge_logo.svg
|
|
66
|
+
.. :target: https://mybinder.org/v2/gh/JannisHoch/copro/dev?filepath=%2Fexample%2Fnb_binder.ipynb
|
|
49
67
|
|
|
50
68
|
Model purpose
|
|
51
69
|
--------------
|
|
@@ -143,6 +161,11 @@ With this (or other) data, the provided configuration-files (cfg-files) can be u
|
|
|
143
161
|
All output is stored in the output directory specified in the cfg-files.
|
|
144
162
|
In the output directory, two folders are created: one name `_REF` for output from the reference run, and `_PROJ` for output for projections.
|
|
145
163
|
|
|
164
|
+
.. important::
|
|
165
|
+
|
|
166
|
+
The example data works with copro version < 2.0.0.
|
|
167
|
+
For the latest version, the data needs to be realigned with the new config-file requirements.
|
|
168
|
+
|
|
146
169
|
Jupyter notebooks
|
|
147
170
|
^^^^^^^^^^^^^^^^^^
|
|
148
171
|
|
|
@@ -209,8 +232,6 @@ CoPro is released under the MIT license.
|
|
|
209
232
|
Authors
|
|
210
233
|
----------------
|
|
211
234
|
|
|
212
|
-
* Jannis M. Hoch (Utrecht University)
|
|
213
|
-
* Sophie de Bruin (Utrecht University
|
|
235
|
+
* Jannis M. Hoch (Utrecht University, now Fathom)
|
|
236
|
+
* Sophie de Bruin (Utrecht University & PBL)
|
|
214
237
|
* Niko Wanders (Utrecht University)
|
|
215
|
-
|
|
216
|
-
Corresponding author: Jannis M. Hoch (j.m.hoch@uu.nl)
|
|
@@ -4,9 +4,6 @@ CoPro
|
|
|
4
4
|
|
|
5
5
|
Welcome to CoPro, a machine-learning tool for conflict risk projections based on climate, environmental, and societal drivers.
|
|
6
6
|
|
|
7
|
-
.. image:: https://travis-ci.com/JannisHoch/copro.svg?branch=dev
|
|
8
|
-
:target: https://travis-ci.com/JannisHoch/copro
|
|
9
|
-
|
|
10
7
|
.. image:: https://img.shields.io/badge/License-MIT-blue.svg
|
|
11
8
|
:target: https://github.com/JannisHoch/copro/blob/dev/LICENSE
|
|
12
9
|
|
|
@@ -14,10 +11,10 @@ Welcome to CoPro, a machine-learning tool for conflict risk projections based on
|
|
|
14
11
|
:target: https://copro.readthedocs.io/en/latest/?badge=latest
|
|
15
12
|
|
|
16
13
|
.. image:: https://img.shields.io/github/v/release/JannisHoch/copro
|
|
17
|
-
:target: https://github.com/JannisHoch/copro/releases/tag/
|
|
14
|
+
:target: https://github.com/JannisHoch/copro/releases/tag/v2.0.1
|
|
18
15
|
|
|
19
16
|
.. image:: https://zenodo.org/badge/254407279.svg
|
|
20
|
-
:target: https://
|
|
17
|
+
:target: https://doi.org/10.5281/zenodo.4061705
|
|
21
18
|
|
|
22
19
|
.. image:: https://badges.frapsoft.com/os/v2/open-source.svg?v=103
|
|
23
20
|
:target: https://github.com/ellerbrock/open-source-badges/
|
|
@@ -25,8 +22,8 @@ Welcome to CoPro, a machine-learning tool for conflict risk projections based on
|
|
|
25
22
|
.. image:: https://joss.theoj.org/papers/1f03334e56413ff71f65092ecc609aa4/status.svg
|
|
26
23
|
:target: https://joss.theoj.org/papers/1f03334e56413ff71f65092ecc609aa4
|
|
27
24
|
|
|
28
|
-
.. image:: https://mybinder.org/badge_logo.svg
|
|
29
|
-
|
|
25
|
+
.. .. image:: https://mybinder.org/badge_logo.svg
|
|
26
|
+
.. :target: https://mybinder.org/v2/gh/JannisHoch/copro/dev?filepath=%2Fexample%2Fnb_binder.ipynb
|
|
30
27
|
|
|
31
28
|
Model purpose
|
|
32
29
|
--------------
|
|
@@ -124,6 +121,11 @@ With this (or other) data, the provided configuration-files (cfg-files) can be u
|
|
|
124
121
|
All output is stored in the output directory specified in the cfg-files.
|
|
125
122
|
In the output directory, two folders are created: one name `_REF` for output from the reference run, and `_PROJ` for output for projections.
|
|
126
123
|
|
|
124
|
+
.. important::
|
|
125
|
+
|
|
126
|
+
The example data works with copro version < 2.0.0.
|
|
127
|
+
For the latest version, the data needs to be realigned with the new config-file requirements.
|
|
128
|
+
|
|
127
129
|
Jupyter notebooks
|
|
128
130
|
^^^^^^^^^^^^^^^^^^
|
|
129
131
|
|
|
@@ -190,8 +192,6 @@ CoPro is released under the MIT license.
|
|
|
190
192
|
Authors
|
|
191
193
|
----------------
|
|
192
194
|
|
|
193
|
-
* Jannis M. Hoch (Utrecht University)
|
|
194
|
-
* Sophie de Bruin (Utrecht University
|
|
195
|
+
* Jannis M. Hoch (Utrecht University, now Fathom)
|
|
196
|
+
* Sophie de Bruin (Utrecht University & PBL)
|
|
195
197
|
* Niko Wanders (Utrecht University)
|
|
196
|
-
|
|
197
|
-
Corresponding author: Jannis M. Hoch (j.m.hoch@uu.nl)
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import warnings
|
|
3
|
+
from configparser import RawConfigParser
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Literal, Union
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import geopandas as gpd
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from copro import nb, utils
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def conflict_in_year_bool(
|
|
16
|
+
config: RawConfigParser,
|
|
17
|
+
conflict_gdf: gpd.GeoDataFrame,
|
|
18
|
+
extent_gdf: gpd.GeoDataFrame,
|
|
19
|
+
sim_year: int,
|
|
20
|
+
out_dir: click.Path,
|
|
21
|
+
poly_identifier="watprovID",
|
|
22
|
+
conflict_identifier="event_id_cnty",
|
|
23
|
+
) -> list:
|
|
24
|
+
"""Creates a list for each timestep with boolean information whether a conflict took place in a polygon or not.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
config (ConfigParser-object): object containing the parsed configuration-settings of the model.
|
|
28
|
+
conflict_gdf (geodataframe): geo-dataframe containing georeferenced information of conflict.
|
|
29
|
+
extent_gdf (geodataframe): geo-dataframe containing one or more polygons \
|
|
30
|
+
with geometry information for which values are extracted.
|
|
31
|
+
sim_year (int): year for which data is extracted.
|
|
32
|
+
out_dir (str): path to output folder. If 'None', no output is stored.
|
|
33
|
+
identifier (str, optional): unique polygon identifier column name. Defaults to 'watprovID'.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
list: list containing 0/1 per polygon depending on conflict occurence.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
click.echo(f"Checking for conflict events which occured in {sim_year}.")
|
|
40
|
+
# select the entries which occured in this year
|
|
41
|
+
temp_sel_year = conflict_gdf.loc[conflict_gdf.year == sim_year]
|
|
42
|
+
if temp_sel_year.empty:
|
|
43
|
+
warnings.warn(
|
|
44
|
+
f"No conflicts were found in sampled conflict data set for year {sim_year}."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# merge the dataframes with polygons and conflict information, creating a sub-set of polygons/regions
|
|
48
|
+
data_merged = gpd.sjoin(temp_sel_year, extent_gdf)
|
|
49
|
+
|
|
50
|
+
# determine the aggregated amount of fatalities in one region (e.g. water province)
|
|
51
|
+
fatalities_per_poly = (
|
|
52
|
+
data_merged[conflict_identifier]
|
|
53
|
+
.groupby(data_merged[poly_identifier])
|
|
54
|
+
.count()
|
|
55
|
+
.to_frame()
|
|
56
|
+
.rename(columns={conflict_identifier: "total_fatalities"})
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
out_dir = os.path.join(out_dir, "files")
|
|
60
|
+
Path.mkdir(Path(out_dir), exist_ok=True)
|
|
61
|
+
|
|
62
|
+
if sim_year == config["general"]["y_end"]:
|
|
63
|
+
_store_boolean_conflict_data_to_csv(
|
|
64
|
+
fatalities_per_poly, extent_gdf, sim_year, out_dir
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# loop through all regions and check if exists in sub-set
|
|
68
|
+
# if so, this means that there was conflict and thus assign value 1
|
|
69
|
+
list_out = []
|
|
70
|
+
for i, _ in extent_gdf.iterrows():
|
|
71
|
+
i_poly = extent_gdf.iloc[i][poly_identifier]
|
|
72
|
+
if i_poly in fatalities_per_poly.index.values:
|
|
73
|
+
list_out.append(1)
|
|
74
|
+
else:
|
|
75
|
+
list_out.append(0)
|
|
76
|
+
|
|
77
|
+
return list_out
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def conflict_in_previous_year_bool(
|
|
81
|
+
conflict_gdf: gpd.GeoDataFrame,
|
|
82
|
+
extent_gdf: gpd.GeoDataFrame,
|
|
83
|
+
sim_year: int,
|
|
84
|
+
check_neighbors: bool = False,
|
|
85
|
+
neighboring_matrix: Union[None, pd.DataFrame] = None,
|
|
86
|
+
poly_identifier="watprovID", # TODO: no kwarg, should come from config
|
|
87
|
+
conflict_identifier="event_id_cnty", # TODO: no kwarg, should come from config
|
|
88
|
+
) -> list:
|
|
89
|
+
"""Creates a list for each timestep with boolean information whether
|
|
90
|
+
a conflict took place in the previous year in a polygon or not.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
conflict_gdf (gpd.GeoDataFrame): georeferenced information of conflict.
|
|
94
|
+
extent_gdf (gpd.GeoDataFrame): geo-dataframe containing one or more polygons \
|
|
95
|
+
with geometry information for which values are extracted.
|
|
96
|
+
sim_year (int): year for which data is extracted.
|
|
97
|
+
check_neighbors (bool, optional): whether to check for conflict in neighbouring polygons. Defaults to False.
|
|
98
|
+
neighboring_matrix (Union[None, pd.DataFrame], optional): dataframe with neighbouring polygons per polygon. \
|
|
99
|
+
Defaults to None.
|
|
100
|
+
identifier (str, optional): unique polygon identifier column name. Defaults to 'watprovID'.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
list: list containing 0/1 per polygon depending on conflict occurence.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
if check_neighbors:
|
|
107
|
+
click.echo("Checking for conflicts in neighboring polygons at t-1")
|
|
108
|
+
else:
|
|
109
|
+
click.echo("Checking for conflict event in polygon at t-1")
|
|
110
|
+
|
|
111
|
+
# TODO: screening whether there is any conflict data in sim_year should be done earlier
|
|
112
|
+
# get conflicts at t-1
|
|
113
|
+
temp_sel_year = conflict_gdf[conflict_gdf.year == sim_year - 1]
|
|
114
|
+
if temp_sel_year.empty:
|
|
115
|
+
warnings.warn(
|
|
116
|
+
f"No conflicts were found in sampled conflict data set for year {sim_year - 1}."
|
|
117
|
+
)
|
|
118
|
+
# merge the dataframes with polygons and conflict information, creating a sub-set of polygons/regions
|
|
119
|
+
data_merged = gpd.sjoin(temp_sel_year, extent_gdf)
|
|
120
|
+
conflicts_per_poly = (
|
|
121
|
+
data_merged[conflict_identifier]
|
|
122
|
+
.groupby(data_merged[poly_identifier])
|
|
123
|
+
.count()
|
|
124
|
+
.to_frame()
|
|
125
|
+
.rename(columns={conflict_identifier: "conflict_count"})
|
|
126
|
+
)
|
|
127
|
+
# NOTE: WORKS UNTIL HERE
|
|
128
|
+
|
|
129
|
+
# loop through all polygons
|
|
130
|
+
list_out = []
|
|
131
|
+
for i in range(len(extent_gdf)):
|
|
132
|
+
i_poly = extent_gdf[poly_identifier].iloc[i]
|
|
133
|
+
# check if polygon is in list with conflict polygons
|
|
134
|
+
if i_poly in conflicts_per_poly.index.values:
|
|
135
|
+
# if so, check if neighboring polygons contain conflict and assign boolean value
|
|
136
|
+
if check_neighbors:
|
|
137
|
+
val = calc_conflicts_nb(i_poly, neighboring_matrix, conflicts_per_poly)
|
|
138
|
+
# append resulting value
|
|
139
|
+
list_out.append(val)
|
|
140
|
+
# if not, assign 1 directly
|
|
141
|
+
else:
|
|
142
|
+
list_out.append(1)
|
|
143
|
+
else:
|
|
144
|
+
# if polygon not in list with conflict polygons, assign 0
|
|
145
|
+
list_out.append(0)
|
|
146
|
+
|
|
147
|
+
return list_out
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def read_projected_conflict(
|
|
151
|
+
extent_gdf: gpd.GeoDataFrame,
|
|
152
|
+
bool_conflict: pd.DataFrame,
|
|
153
|
+
check_neighbors=False,
|
|
154
|
+
neighboring_matrix=None,
|
|
155
|
+
identifier="watprovID",
|
|
156
|
+
) -> list:
|
|
157
|
+
"""Creates a list for each timestep with boolean information
|
|
158
|
+
whether a conflict took place in a polygon or not.
|
|
159
|
+
Input conflict data (`bool_conflict`) must contain an index with IDs
|
|
160
|
+
corresponding with the `watprovID` values of extent_gdf.
|
|
161
|
+
Optionally, the algorithm can be extended to the neighboring polygons.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
extent_gdf (gpd.GeoDataFrame): geo-dataframe containing one or more polygons \
|
|
165
|
+
with geometry information for which values are extracted.
|
|
166
|
+
bool_conflict (pd.DataFrame): dataframe with boolean values (1) for each polygon with conflict.
|
|
167
|
+
check_neighbors (bool, optional): whether or not to check for conflict in neighboring polygons. \
|
|
168
|
+
Defaults to `False`.
|
|
169
|
+
neighboring_matrix (pd.DataFrame, optional): look-up dataframe listing all neighboring polygons. \
|
|
170
|
+
Defaults to `None`.
|
|
171
|
+
identifier (str, optional): unique polygon identifier column name. Defaults to 'watprovID'.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
list: 1 and 0 values for each polygon with conflict respectively without conflict. \
|
|
175
|
+
If `check_neighbors=True`, then 1 if neighboring polygon contains conflict and 0 is not.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
# loop through all polygons and check if exists in sub-set
|
|
179
|
+
list_out = []
|
|
180
|
+
for i in range(len(extent_gdf)):
|
|
181
|
+
i_poly = extent_gdf[identifier].iloc[i]
|
|
182
|
+
if i_poly in bool_conflict.index.values:
|
|
183
|
+
if check_neighbors:
|
|
184
|
+
# determine log-scaled number of conflict events in neighboring polygons
|
|
185
|
+
val = calc_conflicts_nb(i_poly, neighboring_matrix, bool_conflict)
|
|
186
|
+
# append resulting value
|
|
187
|
+
list_out.append(val)
|
|
188
|
+
else:
|
|
189
|
+
list_out.append(1)
|
|
190
|
+
else:
|
|
191
|
+
# if polygon not in list with conflict polygons, assign 0
|
|
192
|
+
list_out.append(0)
|
|
193
|
+
|
|
194
|
+
return list_out
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def calc_conflicts_nb(
|
|
198
|
+
i_poly: int, neighboring_matrix: pd.DataFrame, conflicts_per_poly: pd.DataFrame
|
|
199
|
+
) -> Literal[0, 1]:
|
|
200
|
+
"""Determines whether in the neighbouring polygons of a polygon i_poly conflict took place.
|
|
201
|
+
If so, a value 1 is returned, otherwise 0.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
i_poly (int): ID number of polygon under consideration.
|
|
205
|
+
neighboring_matrix (pd.DataFrame): look-up dataframe listing all neighboring polygons.
|
|
206
|
+
conflicts_per_poly (pd.DataFrame): dataframe with conflict data per polygon.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Literal: 1 if conflict took place in neighboring polygon, 0 if not.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
# find neighbors of this polygon
|
|
213
|
+
nbs = nb.find_neighbors(i_poly, neighboring_matrix)
|
|
214
|
+
|
|
215
|
+
# initiate list
|
|
216
|
+
nb_count = []
|
|
217
|
+
# loop through neighbors
|
|
218
|
+
for k in nbs:
|
|
219
|
+
# check if there was conflict at t-1
|
|
220
|
+
if k in conflicts_per_poly.index.values:
|
|
221
|
+
nb_count.append(1)
|
|
222
|
+
# if more than one neighboring polygon has conflict, return 0
|
|
223
|
+
if np.sum(nb_count) > 0:
|
|
224
|
+
val = 1
|
|
225
|
+
# otherwise, return 0
|
|
226
|
+
else:
|
|
227
|
+
val = 0
|
|
228
|
+
|
|
229
|
+
return val
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def check_for_correct_prediction(
|
|
233
|
+
X_test_ID: np.ndarray,
|
|
234
|
+
X_test_geom: np.ndarray,
|
|
235
|
+
y_test: np.ndarray,
|
|
236
|
+
y_pred: np.ndarray,
|
|
237
|
+
y_prob_0: np.ndarray,
|
|
238
|
+
y_prob_1: np.ndarray,
|
|
239
|
+
) -> pd.DataFrame:
|
|
240
|
+
"""Stacks together the arrays with unique identifier, geometry, test data, and predicted data into a dataframe.
|
|
241
|
+
Contains therefore only the data points used in the test-sample, not in the training-sample.
|
|
242
|
+
Additionally computes whether a correct prediction was made.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
X_test_ID (np.ndarray): _description_
|
|
246
|
+
X_test_geom (np.ndarray): _description_
|
|
247
|
+
y_test (np.ndarray): _description_
|
|
248
|
+
y_pred (np.ndarray): _description_
|
|
249
|
+
y_prob_0 (np.ndarray): _description_
|
|
250
|
+
y_prob_1 (np.ndarray): _description_
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
pd.DataFrame: _description_
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
# stack separate columns horizontally
|
|
257
|
+
arr = np.column_stack((X_test_ID, X_test_geom, y_test, y_pred, y_prob_0, y_prob_1))
|
|
258
|
+
# convert array to dataframe
|
|
259
|
+
df = pd.DataFrame(
|
|
260
|
+
arr, columns=["ID", "geometry", "y_test", "y_pred", "y_prob_0", "y_prob_1"]
|
|
261
|
+
)
|
|
262
|
+
# compute whether a prediction is correct
|
|
263
|
+
# if so, assign 1; otherwise, assign 0
|
|
264
|
+
df["correct_pred"] = np.where(df["y_test"] == df["y_pred"], 1, 0)
|
|
265
|
+
|
|
266
|
+
return df
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _store_boolean_conflict_data_to_csv(
|
|
270
|
+
fatalities_per_poly: pd.DataFrame,
|
|
271
|
+
extent_gdf: gpd.GeoDataFrame,
|
|
272
|
+
sim_year: int,
|
|
273
|
+
out_dir: click.Path,
|
|
274
|
+
identifier="watprovID",
|
|
275
|
+
):
|
|
276
|
+
"""Stores boolean conflict data to csv-file at the end of reference period.
|
|
277
|
+
Used as initial conditions for projections from there.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
fatalities_per_poly (pd.DataFrame): Fatalities per polygon in `sim_year`.
|
|
281
|
+
extent_gdf (gpd.GeoDataFrame): All polygons considered in analysis, also those w/o conflict.
|
|
282
|
+
sim_year (int): Simulation year for which data is stored.
|
|
283
|
+
out_dir (click.Path): Path to output folder.
|
|
284
|
+
identifier (str, optional): Unique polygon identifier column name. Defaults to 'watprovID'.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
# get a 1 for each polygon where there was conflict
|
|
288
|
+
bool_per_poly = fatalities_per_poly / fatalities_per_poly
|
|
289
|
+
# change column name and dtype
|
|
290
|
+
bool_per_poly = bool_per_poly.rename(
|
|
291
|
+
columns={"total_fatalities": "bool_conflict"}
|
|
292
|
+
).astype(int)
|
|
293
|
+
# change index name to fit global_df
|
|
294
|
+
bool_per_poly.index = bool_per_poly.index.rename("ID")
|
|
295
|
+
# get list of all polygon IDs with their geometry information
|
|
296
|
+
global_df = utils.get_ID_geometry_lookup(extent_gdf)
|
|
297
|
+
# merge the boolean info with geometry
|
|
298
|
+
# for all polygons without conflict, set a 0
|
|
299
|
+
click.echo(
|
|
300
|
+
f"Storing boolean conflict map of year {sim_year} \
|
|
301
|
+
to file {os.path.join(out_dir, f'conflicts_in_{sim_year}.csv')}"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
data_stored = pd.merge(bool_per_poly, global_df, on="ID", how="right").dropna()
|
|
305
|
+
data_stored.index = data_stored.index.rename(identifier)
|
|
306
|
+
data_stored = data_stored.drop("geometry", axis=1)
|
|
307
|
+
data_stored = data_stored.astype(int)
|
|
308
|
+
data_stored.to_csv(os.path.join(out_dir, f"conflicts_in_{sim_year}.csv"))
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
import geopandas as gpd
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from sklearn import metrics
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def init_out_dict(scores: Union[list[str], None] = None) -> dict:
|
|
9
|
+
"""Initiates the main model evaluatoin dictionary for a range of model metric scores.
|
|
10
|
+
The scores should match the scores used in the dictioary created in 'evaluation.evaluate_prediction()'.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
scores (list, None): list containing metric scores. If 'None', a default list is used. Defaults to 'None'.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
dict: empty dictionary with metrics as keys.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
if scores is None:
|
|
20
|
+
scores = [
|
|
21
|
+
"Accuracy",
|
|
22
|
+
"Precision",
|
|
23
|
+
"Recall",
|
|
24
|
+
"F1 score",
|
|
25
|
+
"Cohen-Kappa score",
|
|
26
|
+
"Brier loss score",
|
|
27
|
+
"ROC AUC score",
|
|
28
|
+
"AP score",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# initialize empty dictionary with one emtpy list per score
|
|
32
|
+
out_dict = {}
|
|
33
|
+
for score in scores:
|
|
34
|
+
out_dict[score] = []
|
|
35
|
+
|
|
36
|
+
return out_dict
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def fill_out_dict(
|
|
40
|
+
out_dict: dict,
|
|
41
|
+
eval_dict: dict,
|
|
42
|
+
):
|
|
43
|
+
"""Appends the computed metric score per run to the main output dictionary.
|
|
44
|
+
All metrics are initialized in init_out_dict().
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
out_dict (dict): main output dictionary.
|
|
48
|
+
eval_dict (dict): dictionary containing scores per simulation.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
dict: dictionary with collected scores for each simulation
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
for key in out_dict:
|
|
55
|
+
out_dict[key].append(eval_dict[key])
|
|
56
|
+
|
|
57
|
+
return out_dict
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def polygon_model_accuracy(
|
|
61
|
+
df: pd.DataFrame, global_df: pd.DataFrame, make_proj=False
|
|
62
|
+
) -> gpd.GeoDataFrame:
|
|
63
|
+
"""Determines a range of model accuracy values for each polygon.
|
|
64
|
+
Reduces dataframe with results from each simulation to values per unique polygon identifier.
|
|
65
|
+
Determines the total number of predictions made per polygon
|
|
66
|
+
as well as fraction of correct predictions made for overall and conflict-only data.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
df (pd.DataFrame): output dataframe containing results of all simulations.
|
|
70
|
+
global_df (pd.DataFrame): global look-up dataframe to associate unique identifier with geometry.
|
|
71
|
+
make_proj (bool, optional): whether or not this function is used to make a projection. \
|
|
72
|
+
If True, a couple of calculations are skipped as no observed data is available for projections. \
|
|
73
|
+
Defaults to 'False'.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
gpd.GeoDataFrame: model accuracy data per polygon.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
# - create a dataframe containing the number of occurence per ID
|
|
80
|
+
ID_count = (
|
|
81
|
+
df.ID.value_counts().to_frame().rename(columns={"count": "nr_predictions"})
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
df_count = pd.DataFrame()
|
|
85
|
+
|
|
86
|
+
# - per polygon ID, compute sum of overall correct predictions and rename column name
|
|
87
|
+
if not make_proj:
|
|
88
|
+
df_count["nr_correct_predictions"] = df.correct_pred.groupby(df.ID).sum()
|
|
89
|
+
|
|
90
|
+
# - per polygon ID, compute sum of all conflict data points and add to dataframe
|
|
91
|
+
if not make_proj:
|
|
92
|
+
df_count["nr_observed_conflicts"] = df.y_test.groupby(df.ID).sum().astype(float)
|
|
93
|
+
|
|
94
|
+
# - per polygon ID, compute sum of all conflict data points and add to dataframe
|
|
95
|
+
df_count["nr_predicted_conflicts"] = df.y_pred.groupby(df.ID).sum().astype(float)
|
|
96
|
+
# - per polygon ID, compute average probability that conflict occurs
|
|
97
|
+
df_count["min_prob_1"] = pd.to_numeric(df.y_prob_1).groupby(df.ID).min()
|
|
98
|
+
df_count["probability_of_conflict"] = (
|
|
99
|
+
pd.to_numeric(df.y_prob_1).groupby(df.ID).mean()
|
|
100
|
+
)
|
|
101
|
+
df_count["max_prob_1"] = pd.to_numeric(df.y_prob_1).groupby(df.ID).max()
|
|
102
|
+
|
|
103
|
+
# - merge the two dataframes with ID as key
|
|
104
|
+
df_temp = pd.merge(ID_count, df_count, on="ID")
|
|
105
|
+
|
|
106
|
+
# - compute average correct prediction rate by dividing sum of correct predictions with number of all predicionts
|
|
107
|
+
if not make_proj:
|
|
108
|
+
df_temp["fraction_correct_predictions"] = (
|
|
109
|
+
df_temp.nr_correct_predictions / df_temp.nr_predictions
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# - compute average correct prediction rate by dividing sum of correct predictions with number of all predicionts
|
|
113
|
+
df_temp["chance_of_conflict"] = (
|
|
114
|
+
df_temp.nr_predicted_conflicts / df_temp.nr_predictions
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# - merge with global dataframe containing IDs and geometry, and keep only those polygons occuring in test sample
|
|
118
|
+
df_hit = pd.merge(df_temp, global_df, on="ID", how="left")
|
|
119
|
+
# #- convert to geodataframe
|
|
120
|
+
gdf_hit = gpd.GeoDataFrame(df_hit, geometry=df_hit.geometry)
|
|
121
|
+
|
|
122
|
+
return gdf_hit
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def evaluate_prediction(y_test: list, y_pred: list, y_prob: list) -> dict:
|
|
126
|
+
"""Computes a range of model evaluation metrics and appends the resulting scores to a dictionary.
|
|
127
|
+
This is done for each model execution separately.
|
|
128
|
+
Output will be stored to stderr if possible.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
y_test (list): list containing test-sample conflict data.
|
|
132
|
+
y_pred (list): list containing predictions.
|
|
133
|
+
y_prob (array): array resulting probabilties of predictions.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
dict: dictionary with scores for each simulation
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
# compute value per evaluation metric and assign to list
|
|
140
|
+
eval_dict = {
|
|
141
|
+
"Accuracy": metrics.accuracy_score(y_test, y_pred),
|
|
142
|
+
"Precision": metrics.precision_score(y_test, y_pred),
|
|
143
|
+
"Recall": metrics.recall_score(y_test, y_pred),
|
|
144
|
+
"F1 score": metrics.f1_score(y_test, y_pred),
|
|
145
|
+
"Cohen-Kappa score": metrics.cohen_kappa_score(y_test, y_pred),
|
|
146
|
+
"Brier loss score": metrics.brier_score_loss(y_test, y_prob[:, 1]),
|
|
147
|
+
"ROC AUC score": metrics.roc_auc_score(y_test, y_prob[:, 1]),
|
|
148
|
+
"AP score": metrics.average_precision_score(y_test, y_prob[:, 1]),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return eval_dict
|