morocco-communes 1.8.0__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.
- morocco_communes-1.8.0/.gitignore +14 -0
- morocco_communes-1.8.0/LICENSE +27 -0
- morocco_communes-1.8.0/PKG-INFO +84 -0
- morocco_communes-1.8.0/README.md +61 -0
- morocco_communes-1.8.0/build_data.py +104 -0
- morocco_communes-1.8.0/pyproject.toml +37 -0
- morocco_communes-1.8.0/src/morocco_communes/__init__.py +51 -0
- morocco_communes-1.8.0/src/morocco_communes/_data.py +178 -0
- morocco_communes-1.8.0/src/morocco_communes/_version.py +3 -0
- morocco_communes-1.8.0/src/morocco_communes/data/attributes/arrondissements.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/attributes/cercles.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/attributes/communes.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/attributes/provinces.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/attributes/regions.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/crosswalk/2014-2024.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/economy/establishments.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/fields/economy.json +1 -0
- morocco_communes-1.8.0/src/morocco_communes/data/fields/housing.json +1 -0
- morocco_communes-1.8.0/src/morocco_communes/data/fields/indicators.json +1 -0
- morocco_communes-1.8.0/src/morocco_communes/data/fields/indicators2014.json +1 -0
- morocco_communes-1.8.0/src/morocco_communes/data/housing/dwellings.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/indicators/households.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/indicators/people.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/indicators2014/households.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/indicators2014/people.csv.gz +0 -0
- morocco_communes-1.8.0/src/morocco_communes/data/sources.json +1 -0
- morocco_communes-1.8.0/src/morocco_communes/py.typed +0 -0
- morocco_communes-1.8.0/tests/python.test.ts +30 -0
- morocco_communes-1.8.0/tests/test_package.py +164 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
.cache/
|
|
3
|
+
dist/
|
|
4
|
+
.DS_Store
|
|
5
|
+
.superpowers/
|
|
6
|
+
docs/
|
|
7
|
+
.wrangler/
|
|
8
|
+
packages/morocco-communes/dist/
|
|
9
|
+
packages/morocco-communes-py/src/morocco_communes/data/
|
|
10
|
+
packages/morocco-communes-py/src/morocco_communes/_version.py
|
|
11
|
+
packages/morocco-communes-py/dist/
|
|
12
|
+
__pycache__/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
evals/results/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
The code in this package is under the MIT License:
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ousama Ajebbar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
The data (codes, names and population) comes from the Haut-Commissariat au Plan,
|
|
24
|
+
Recensement Général de la Population et de l'Habitat 2024 and 2014, a government
|
|
25
|
+
publication. HCP's terms of use allow reuse, commercial included, on CC BY 4.0 terms:
|
|
26
|
+
credit the Haut-Commissariat au Plan and say what was changed.
|
|
27
|
+
https://www.hcp.ma/Conditions-generales-d-utilisation-Version-1-0_a2194.html
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: morocco-communes
|
|
3
|
+
Version: 1.8.0
|
|
4
|
+
Summary: Morocco's régions, provinces, cercles, communes and arrondissements, with the 2024 and 2014 census figures, offline
|
|
5
|
+
Project-URL: Homepage, https://communes.pages.dev
|
|
6
|
+
Project-URL: Documentation, https://communes.pages.dev/docs/python/
|
|
7
|
+
Project-URL: Source, https://github.com/zkousama/morocco-communes
|
|
8
|
+
Project-URL: Issues, https://github.com/zkousama/morocco-communes/issues
|
|
9
|
+
Author: Ousama Ajebbar
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: administrative divisions,census,communes,hcp,maroc,morocco,recensement,rgph
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
17
|
+
Classifier: Topic :: Sociology
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Provides-Extra: pandas
|
|
21
|
+
Requires-Dist: pandas>=1.5; extra == 'pandas'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# morocco-communes
|
|
25
|
+
|
|
26
|
+
Morocco's 12 régions, 83 provinces and préfectures, 213 cercles, 1,503 communes and 41
|
|
27
|
+
arrondissements, with HCP's codes, names in French and Arabic, the 2024 and 2014 census
|
|
28
|
+
figures, and the establishments the 2024 census mapped. It reads from files inside the
|
|
29
|
+
package, so it works offline.
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
pip install "morocco-communes[pandas]"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import morocco_communes as mc
|
|
37
|
+
|
|
38
|
+
communes = mc.communes() # 1,503 rows
|
|
39
|
+
communes[communes["region_code"] == "01"] # the 146 in Tanger-Tétouan-Al Hoceima
|
|
40
|
+
|
|
41
|
+
people = mc.indicators("people") # the 2024 census, by unit, area and sex
|
|
42
|
+
economy = mc.economy() # the establishments, by unit
|
|
43
|
+
housing = mc.housing() # the urban dwellings, by unit
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Every table is a pandas DataFrame. With `as_frame=False` it's a list of dicts instead, and
|
|
47
|
+
then the package needs nothing but the standard library.
|
|
48
|
+
|
|
49
|
+
## Tables
|
|
50
|
+
|
|
51
|
+
| Call | Rows |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `regions()`, `provinces()`, `cercles()`, `communes()`, `arrondissements()` | one per unit |
|
|
54
|
+
| `indicators(subject, census)` | one per unit and area, and per sex for people |
|
|
55
|
+
| `economy()` | one per unit, from the country down to the commune |
|
|
56
|
+
| `housing()` | one per unit with an urban area, 784 of them |
|
|
57
|
+
| `crosswalk()` | one per commune renumbered in 2015 |
|
|
58
|
+
|
|
59
|
+
`indicators` takes `"people"` or `"households"`, and `census` `"2024"` or `"2014"`. The
|
|
60
|
+
2024 census asks 73 of the 2014 questions the same way, so those columns subtract.
|
|
61
|
+
|
|
62
|
+
`fields()` says what each column measures, with HCP's own heading for it and its unit, and
|
|
63
|
+
for 2024 the concept HCP defines it under and its wording. It takes `"indicators"`,
|
|
64
|
+
`"indicators2014"`, `"economy"` or `"housing"`. `sources()` gives each
|
|
65
|
+
workbook's URL, its SHA-256 and the date it was read, and `version` is the dataset's.
|
|
66
|
+
|
|
67
|
+
## Codes
|
|
68
|
+
|
|
69
|
+
A unit is identified by its HCP code, and the code is the hierarchy: `01.511.01.0` is a
|
|
70
|
+
commune of province `01.511`, in région `01`. Codes stay strings, so `code_digits` keeps
|
|
71
|
+
the leading zeros that make it 9 digits wide.
|
|
72
|
+
|
|
73
|
+
## What's elsewhere
|
|
74
|
+
|
|
75
|
+
Boundaries, which communes border which, search, the urban centres and a commune lookup by
|
|
76
|
+
point are in the [repository](https://github.com/zkousama/morocco-communes) and its API.
|
|
77
|
+
Those all come from OpenStreetMap under ODbL, so they aren't in this package, and neither
|
|
78
|
+
are the area and density fields derived from them. `data/v1/geometry/adjacency.csv` is the
|
|
79
|
+
contiguity graph if you want one: `pandas.read_csv` takes it straight.
|
|
80
|
+
|
|
81
|
+
## Licence
|
|
82
|
+
|
|
83
|
+
The code is MIT. The data is the Haut-Commissariat au Plan's, from RGPH 2024 and RGPH
|
|
84
|
+
2014, reusable on CC BY 4.0 terms: credit it and say what you changed.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# morocco-communes
|
|
2
|
+
|
|
3
|
+
Morocco's 12 régions, 83 provinces and préfectures, 213 cercles, 1,503 communes and 41
|
|
4
|
+
arrondissements, with HCP's codes, names in French and Arabic, the 2024 and 2014 census
|
|
5
|
+
figures, and the establishments the 2024 census mapped. It reads from files inside the
|
|
6
|
+
package, so it works offline.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install "morocco-communes[pandas]"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import morocco_communes as mc
|
|
14
|
+
|
|
15
|
+
communes = mc.communes() # 1,503 rows
|
|
16
|
+
communes[communes["region_code"] == "01"] # the 146 in Tanger-Tétouan-Al Hoceima
|
|
17
|
+
|
|
18
|
+
people = mc.indicators("people") # the 2024 census, by unit, area and sex
|
|
19
|
+
economy = mc.economy() # the establishments, by unit
|
|
20
|
+
housing = mc.housing() # the urban dwellings, by unit
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Every table is a pandas DataFrame. With `as_frame=False` it's a list of dicts instead, and
|
|
24
|
+
then the package needs nothing but the standard library.
|
|
25
|
+
|
|
26
|
+
## Tables
|
|
27
|
+
|
|
28
|
+
| Call | Rows |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `regions()`, `provinces()`, `cercles()`, `communes()`, `arrondissements()` | one per unit |
|
|
31
|
+
| `indicators(subject, census)` | one per unit and area, and per sex for people |
|
|
32
|
+
| `economy()` | one per unit, from the country down to the commune |
|
|
33
|
+
| `housing()` | one per unit with an urban area, 784 of them |
|
|
34
|
+
| `crosswalk()` | one per commune renumbered in 2015 |
|
|
35
|
+
|
|
36
|
+
`indicators` takes `"people"` or `"households"`, and `census` `"2024"` or `"2014"`. The
|
|
37
|
+
2024 census asks 73 of the 2014 questions the same way, so those columns subtract.
|
|
38
|
+
|
|
39
|
+
`fields()` says what each column measures, with HCP's own heading for it and its unit, and
|
|
40
|
+
for 2024 the concept HCP defines it under and its wording. It takes `"indicators"`,
|
|
41
|
+
`"indicators2014"`, `"economy"` or `"housing"`. `sources()` gives each
|
|
42
|
+
workbook's URL, its SHA-256 and the date it was read, and `version` is the dataset's.
|
|
43
|
+
|
|
44
|
+
## Codes
|
|
45
|
+
|
|
46
|
+
A unit is identified by its HCP code, and the code is the hierarchy: `01.511.01.0` is a
|
|
47
|
+
commune of province `01.511`, in région `01`. Codes stay strings, so `code_digits` keeps
|
|
48
|
+
the leading zeros that make it 9 digits wide.
|
|
49
|
+
|
|
50
|
+
## What's elsewhere
|
|
51
|
+
|
|
52
|
+
Boundaries, which communes border which, search, the urban centres and a commune lookup by
|
|
53
|
+
point are in the [repository](https://github.com/zkousama/morocco-communes) and its API.
|
|
54
|
+
Those all come from OpenStreetMap under ODbL, so they aren't in this package, and neither
|
|
55
|
+
are the area and density fields derived from them. `data/v1/geometry/adjacency.csv` is the
|
|
56
|
+
contiguity graph if you want one: `pandas.read_csv` takes it straight.
|
|
57
|
+
|
|
58
|
+
## Licence
|
|
59
|
+
|
|
60
|
+
The code is MIT. The data is the Haut-Commissariat au Plan's, from RGPH 2024 and RGPH
|
|
61
|
+
2014, reusable on CC BY 4.0 terms: credit it and say what you changed.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Builds the Python package's data from data/v1: the units, the census figures for both
|
|
3
|
+
censuses, the 2024 establishments and the crosswalk, gzipped so an install stays small.
|
|
4
|
+
|
|
5
|
+
The two fields that come from OpenStreetMap, area and density, are left out of the
|
|
6
|
+
communes table, as they are from the npm package. They're under ODbL, whose share-alike
|
|
7
|
+
terms would then cover the package; everything here is census data, attributed.
|
|
8
|
+
|
|
9
|
+
python3 packages/morocco-communes-py/build_data.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import gzip
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
HERE = Path(__file__).parent
|
|
19
|
+
DATA = Path("data/v1")
|
|
20
|
+
OUT = HERE / "src" / "morocco_communes" / "data"
|
|
21
|
+
|
|
22
|
+
# source path under data/v1 -> path inside the package
|
|
23
|
+
TABLES = {
|
|
24
|
+
"attributes/regions.csv": "attributes/regions.csv.gz",
|
|
25
|
+
"attributes/provinces.csv": "attributes/provinces.csv.gz",
|
|
26
|
+
"attributes/cercles.csv": "attributes/cercles.csv.gz",
|
|
27
|
+
"attributes/communes.csv": "attributes/communes.csv.gz",
|
|
28
|
+
"attributes/arrondissements.csv": "attributes/arrondissements.csv.gz",
|
|
29
|
+
"indicators/people.csv": "indicators/people.csv.gz",
|
|
30
|
+
"indicators/households.csv": "indicators/households.csv.gz",
|
|
31
|
+
"indicators/2014/people.csv": "indicators2014/people.csv.gz",
|
|
32
|
+
"indicators/2014/households.csv": "indicators2014/households.csv.gz",
|
|
33
|
+
"economy/establishments.csv": "economy/establishments.csv.gz",
|
|
34
|
+
"housing/dwellings.csv": "housing/dwellings.csv.gz",
|
|
35
|
+
"crosswalk/2014-2024.csv": "crosswalk/2014-2024.csv.gz",
|
|
36
|
+
}
|
|
37
|
+
DOCUMENTS = {
|
|
38
|
+
"indicators/fields.json": "fields/indicators.json",
|
|
39
|
+
"indicators/2014/fields.json": "fields/indicators2014.json",
|
|
40
|
+
"economy/fields.json": "fields/economy.json",
|
|
41
|
+
"housing/fields.json": "fields/housing.json",
|
|
42
|
+
"sources.json": "sources.json",
|
|
43
|
+
}
|
|
44
|
+
# Derived from OpenStreetMap, so out, along with the boundaries themselves.
|
|
45
|
+
DROP = {"attributes/communes.csv": ["area_km2", "density_2024"]}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def rows(path: Path):
|
|
49
|
+
"""The file's lines, without the byte-order mark Excel wants."""
|
|
50
|
+
text = path.read_text(encoding="utf-8-sig")
|
|
51
|
+
return text.splitlines(keepends=True)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def drop_columns(lines: list[str], columns: list[str]) -> list[str]:
|
|
55
|
+
"""Takes named columns out of a CSV whose values never contain a comma or a quote."""
|
|
56
|
+
header = lines[0].rstrip("\r\n").split(",")
|
|
57
|
+
for name in columns:
|
|
58
|
+
if name not in header:
|
|
59
|
+
raise SystemExit(f"{name} is not a column of this file; its columns are {', '.join(header)}")
|
|
60
|
+
keep = [i for i, name in enumerate(header) if name not in columns]
|
|
61
|
+
out = []
|
|
62
|
+
for line in lines:
|
|
63
|
+
if '"' in line:
|
|
64
|
+
raise SystemExit("a quoted field: this needs a real CSV reader rather than a split")
|
|
65
|
+
fields = line.rstrip("\r\n").split(",")
|
|
66
|
+
out.append(",".join(fields[i] for i in keep) + "\n")
|
|
67
|
+
return out
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main() -> None:
|
|
71
|
+
if not DATA.is_dir():
|
|
72
|
+
raise SystemExit(f"run this from the repository root; {DATA} is not there")
|
|
73
|
+
if OUT.exists():
|
|
74
|
+
shutil.rmtree(OUT)
|
|
75
|
+
|
|
76
|
+
for source, target in TABLES.items():
|
|
77
|
+
lines = rows(DATA / source)
|
|
78
|
+
if source in DROP:
|
|
79
|
+
lines = drop_columns(lines, DROP[source])
|
|
80
|
+
path = OUT / target
|
|
81
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
# mtime 0, so the same data builds the same bytes.
|
|
83
|
+
with gzip.GzipFile(path, "wb", mtime=0) as f:
|
|
84
|
+
f.write("".join(lines).encode("utf-8"))
|
|
85
|
+
|
|
86
|
+
for source, target in DOCUMENTS.items():
|
|
87
|
+
path = OUT / target
|
|
88
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
path.write_text(json.dumps(json.loads((DATA / source).read_text(encoding="utf-8")), ensure_ascii=False), encoding="utf-8")
|
|
90
|
+
|
|
91
|
+
version = json.loads((DATA / "sources.json").read_text(encoding="utf-8"))["datasetVersion"]
|
|
92
|
+
declared = re.search(r'^version = "(.+)"$', (HERE / "pyproject.toml").read_text(encoding="utf-8"), re.M)
|
|
93
|
+
if not declared:
|
|
94
|
+
raise SystemExit("pyproject.toml has no version line")
|
|
95
|
+
if declared.group(1) != version:
|
|
96
|
+
raise SystemExit(f"pyproject.toml says {declared.group(1)}, the dataset is {version}; bump it deliberately")
|
|
97
|
+
(OUT.parent / "_version.py").write_text(f'"""Generated by build.py. Do not edit."""\n\n__version__ = "{version}"\n', encoding="utf-8")
|
|
98
|
+
|
|
99
|
+
total = sum(p.stat().st_size for p in OUT.rglob("*") if p.is_file())
|
|
100
|
+
print(f"python package: {len(TABLES)} tables and {len(DOCUMENTS)} documents, {total / 1024:.0f} KB, version {version}")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "morocco-communes"
|
|
7
|
+
version = "1.8.0"
|
|
8
|
+
description = "Morocco's régions, provinces, cercles, communes and arrondissements, with the 2024 and 2014 census figures, offline"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Ousama Ajebbar" }]
|
|
14
|
+
keywords = ["morocco", "maroc", "communes", "census", "recensement", "rgph", "hcp", "administrative divisions"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
20
|
+
"Topic :: Sociology",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
pandas = ["pandas>=1.5"]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://communes.pages.dev"
|
|
29
|
+
Documentation = "https://communes.pages.dev/docs/python/"
|
|
30
|
+
Source = "https://github.com/zkousama/morocco-communes"
|
|
31
|
+
Issues = "https://github.com/zkousama/morocco-communes/issues"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/morocco_communes"]
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.sdist]
|
|
37
|
+
include = ["src", "tests", "README.md", "LICENSE", "build_data.py", "pyproject.toml"]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Morocco's administrative divisions and census figures, offline.
|
|
3
|
+
|
|
4
|
+
import morocco_communes as mc
|
|
5
|
+
|
|
6
|
+
mc.communes() # 1,503 rows, with 2024 and 2014 population
|
|
7
|
+
mc.indicators("people") # the 2024 census, by unit, area and sex
|
|
8
|
+
mc.economy() # the establishments the 2024 census mapped
|
|
9
|
+
mc.housing() # the urban dwellings it counted
|
|
10
|
+
|
|
11
|
+
Every table comes back as a pandas DataFrame, or as a list of dicts with as_frame=False,
|
|
12
|
+
which needs nothing but the standard library. `fields()` says what each column measures.
|
|
13
|
+
|
|
14
|
+
The figures are the Haut-Commissariat au Plan's, from RGPH 2024 and RGPH 2014. Credit it
|
|
15
|
+
when you publish them. The boundaries are in the repository and its API instead: they come
|
|
16
|
+
from OpenStreetMap under ODbL, and so are the area and density fields derived from them.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from ._data import (
|
|
20
|
+
arrondissements,
|
|
21
|
+
cercles,
|
|
22
|
+
communes,
|
|
23
|
+
crosswalk,
|
|
24
|
+
economy,
|
|
25
|
+
fields,
|
|
26
|
+
housing,
|
|
27
|
+
indicators,
|
|
28
|
+
provinces,
|
|
29
|
+
regions,
|
|
30
|
+
sources,
|
|
31
|
+
)
|
|
32
|
+
from ._version import __version__
|
|
33
|
+
|
|
34
|
+
#: The dataset's version, which is also the package's.
|
|
35
|
+
version = __version__
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"arrondissements",
|
|
39
|
+
"cercles",
|
|
40
|
+
"communes",
|
|
41
|
+
"crosswalk",
|
|
42
|
+
"economy",
|
|
43
|
+
"fields",
|
|
44
|
+
"housing",
|
|
45
|
+
"indicators",
|
|
46
|
+
"provinces",
|
|
47
|
+
"regions",
|
|
48
|
+
"sources",
|
|
49
|
+
"version",
|
|
50
|
+
"__version__",
|
|
51
|
+
]
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reads the tables the package ships. Each one comes back as a pandas DataFrame, or as a
|
|
3
|
+
list of dicts when pandas isn't installed or isn't wanted.
|
|
4
|
+
|
|
5
|
+
The codes are strings and stay strings: 01.511.01.0 is a commune, and its padded form
|
|
6
|
+
001511010 would lose its leading zeros as a number.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import csv
|
|
12
|
+
import gzip
|
|
13
|
+
import json
|
|
14
|
+
from importlib import resources
|
|
15
|
+
from typing import Any, Iterator
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"regions",
|
|
19
|
+
"provinces",
|
|
20
|
+
"cercles",
|
|
21
|
+
"communes",
|
|
22
|
+
"arrondissements",
|
|
23
|
+
"indicators",
|
|
24
|
+
"economy",
|
|
25
|
+
"housing",
|
|
26
|
+
"crosswalk",
|
|
27
|
+
"fields",
|
|
28
|
+
"sources",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
#: Columns that are identifiers rather than numbers, wherever they appear.
|
|
32
|
+
CODES = frozenset(
|
|
33
|
+
{
|
|
34
|
+
"code",
|
|
35
|
+
"code_digits",
|
|
36
|
+
"region_code",
|
|
37
|
+
"province_code",
|
|
38
|
+
"cercle_code",
|
|
39
|
+
"commune_code",
|
|
40
|
+
"code_2024",
|
|
41
|
+
"code_2014",
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
CENSUSES = ("2024", "2014")
|
|
46
|
+
SUBJECTS = ("people", "households")
|
|
47
|
+
DICTIONARIES = ("indicators", "indicators2014", "economy", "housing")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _path(*parts: str):
|
|
51
|
+
return resources.files("morocco_communes").joinpath("data", *parts)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _header(name: str) -> list[str]:
|
|
55
|
+
with gzip.open(_path(name), "rt", encoding="utf-8") as f:
|
|
56
|
+
return next(csv.reader(f))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _cell(value: str) -> Any:
|
|
60
|
+
"""A CSV cell as what it holds: a whole number, a decimal, or None for an empty one."""
|
|
61
|
+
if value == "":
|
|
62
|
+
return None
|
|
63
|
+
try:
|
|
64
|
+
return int(value)
|
|
65
|
+
except ValueError:
|
|
66
|
+
pass
|
|
67
|
+
try:
|
|
68
|
+
return float(value)
|
|
69
|
+
except ValueError:
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _dicts(name: str) -> list[dict[str, Any]]:
|
|
74
|
+
with gzip.open(_path(name), "rt", encoding="utf-8", newline="") as f:
|
|
75
|
+
return [{k: ((v or None) if k in CODES else _cell(v)) for k, v in row.items()} for row in csv.DictReader(f)]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _pandas():
|
|
79
|
+
try:
|
|
80
|
+
import pandas as pd
|
|
81
|
+
except ModuleNotFoundError as err: # pragma: no cover - depends on the environment
|
|
82
|
+
raise ModuleNotFoundError(
|
|
83
|
+
"reading a table as a DataFrame needs pandas: pip install 'morocco-communes[pandas]', "
|
|
84
|
+
"or pass as_frame=False for a list of dicts"
|
|
85
|
+
) from err
|
|
86
|
+
return pd
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _frame(name: str):
|
|
90
|
+
pd = _pandas()
|
|
91
|
+
text = frozenset(_header(name)) & CODES
|
|
92
|
+
with gzip.open(_path(name), "rb") as f:
|
|
93
|
+
return pd.read_csv(f, dtype={column: "string" for column in text})
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _table(name: str, as_frame: bool):
|
|
97
|
+
return _frame(name) if as_frame else _dicts(name)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def regions(as_frame: bool = True):
|
|
101
|
+
"""The 12 régions, with their 2024 population and how much they hold."""
|
|
102
|
+
return _table("attributes/regions.csv.gz", as_frame)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def provinces(as_frame: bool = True):
|
|
106
|
+
"""The 83 provinces, préfectures and préfectures d'arrondissements."""
|
|
107
|
+
return _table("attributes/provinces.csv.gz", as_frame)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def cercles(as_frame: bool = True):
|
|
111
|
+
"""The 213 cercles, which group the rural communes of a province."""
|
|
112
|
+
return _table("attributes/cercles.csv.gz", as_frame)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def communes(as_frame: bool = True):
|
|
116
|
+
"""The 1,503 communes, with their type, parents, and population in 2024 and 2014."""
|
|
117
|
+
return _table("attributes/communes.csv.gz", as_frame)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def arrondissements(as_frame: bool = True):
|
|
121
|
+
"""The 41 arrondissements of the 6 cities divided into them."""
|
|
122
|
+
return _table("attributes/arrondissements.csv.gz", as_frame)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def indicators(subject: str = "people", census: str = "2024", as_frame: bool = True):
|
|
126
|
+
"""
|
|
127
|
+
HCP's census figures: a row per unit and area for households, and per unit, area and
|
|
128
|
+
sex for people. `subject` is people or households, `census` 2024 or 2014.
|
|
129
|
+
"""
|
|
130
|
+
if subject not in SUBJECTS:
|
|
131
|
+
raise ValueError(f"subject is people or households, not {subject!r}")
|
|
132
|
+
if census not in CENSUSES:
|
|
133
|
+
raise ValueError(f"census is 2024 or 2014, not {census!r}")
|
|
134
|
+
folder = "indicators" if census == "2024" else "indicators2014"
|
|
135
|
+
return _table(f"{folder}/{subject}.csv.gz", as_frame)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def economy(as_frame: bool = True):
|
|
139
|
+
"""The economic establishments the 2024 census mapped, a row per unit."""
|
|
140
|
+
return _table("economy/establishments.csv.gz", as_frame)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def housing(as_frame: bool = True):
|
|
144
|
+
"""
|
|
145
|
+
The urban housing stock the 2024 census counted, a row per unit that has one. It counts
|
|
146
|
+
dwellings rather than households, and only in towns, so a unit with no urban area has
|
|
147
|
+
no row.
|
|
148
|
+
"""
|
|
149
|
+
return _table("housing/dwellings.csv.gz", as_frame)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def crosswalk(as_frame: bool = True):
|
|
153
|
+
"""The 207 communes renumbered in 2015, each 2024 code beside its 2014 one."""
|
|
154
|
+
return _table("crosswalk/2014-2024.csv.gz", as_frame)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def fields(dataset: str = "indicators", as_frame: bool = True):
|
|
158
|
+
"""
|
|
159
|
+
What each column of a table measures: its path, its label, HCP's own heading for it and
|
|
160
|
+
its unit. `dataset` is indicators, indicators2014 or economy.
|
|
161
|
+
"""
|
|
162
|
+
if dataset not in DICTIONARIES:
|
|
163
|
+
raise ValueError(f"dataset is one of {', '.join(DICTIONARIES)}, not {dataset!r}")
|
|
164
|
+
document = json.loads(_path("fields", f"{dataset}.json").read_text(encoding="utf-8"))
|
|
165
|
+
rows = document["fields"] if "fields" in document else [*document["people"], *document["households"]]
|
|
166
|
+
return _pandas().DataFrame(rows) if as_frame else rows
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def sources() -> dict[str, Any]:
|
|
170
|
+
"""Where the data came from: each workbook's URL, digest, licence and the date it was read."""
|
|
171
|
+
return json.loads(_path("sources.json").read_text(encoding="utf-8"))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _tables() -> Iterator[str]:
|
|
175
|
+
"""Every table the package ships, for the tests."""
|
|
176
|
+
for folder in ("attributes", "indicators", "indicators2014", "economy", "housing", "crosswalk"):
|
|
177
|
+
for path in sorted(p.name for p in _path(folder).iterdir()):
|
|
178
|
+
yield f"{folder}/{path}"
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"census": "2024", "source": {"id": "hcp-2024-establishments", "url": "https://www.hcp.ma/file/242672/"}, "fields": [{"path": "establishments.total", "column": "establishments_total", "topic": "establishments", "key": "total", "label": "Establishments mapped", "heading": "Nombre total des établissements géoréférencés", "unit": "establishments"}, {"path": "establishments.publicServices", "column": "establishments_public_services", "topic": "establishments", "key": "publicServices", "label": "Public service establishments", "heading": "Nombre d'établissements de services publics", "unit": "establishments"}, {"path": "establishments.nonProfit", "column": "establishments_non_profit", "topic": "establishments", "key": "nonProfit", "label": "Non-profit establishments, in premises of their own", "heading": "Nombre d'établissements à but non lucratif (Associations opérant dans des locaux indépendants)", "unit": "establishments"}, {"path": "establishments.weeklySouks", "column": "establishments_weekly_souks", "topic": "establishments", "key": "weeklySouks", "label": "Weekly souks in use", "heading": "Nombre de Souks hebodomadaires actifs", "unit": "souks"}, {"path": "establishments.business", "column": "establishments_business", "topic": "establishments", "key": "business", "label": "Businesses", "heading": "Nombre d'établissements à but lucratif (entreprises et leurs établissements hors secteur de l'agriculture)", "category": "Nombre d'établissements", "unit": "establishments"}, {"path": "establishments.jobs", "column": "establishments_jobs", "topic": "establishments", "key": "jobs", "label": "Permanent jobs in those businesses", "heading": "Nombre d'établissements à but lucratif (entreprises et leurs établissements hors secteur de l'agriculture)", "category": "Emplois permanents occupés par ces établissements", "unit": "jobs"}, {"path": "sector.industry", "column": "sector_industry", "topic": "sector", "key": "industry", "label": "Industry", "heading": "Nombre d'établissements à but lucratif : par secteur d'activité", "category": "Industrie", "unit": "establishments"}, {"path": "sector.construction", "column": "sector_construction", "topic": "sector", "key": "construction", "label": "Construction", "heading": "Nombre d'établissements à but lucratif : par secteur d'activité", "category": "Construction", "unit": "establishments"}, {"path": "sector.commerce", "column": "sector_commerce", "topic": "sector", "key": "commerce", "label": "Commerce", "heading": "Nombre d'établissements à but lucratif : par secteur d'activité", "category": "Commerce", "unit": "establishments"}, {"path": "sector.services", "column": "sector_services", "topic": "sector", "key": "services", "label": "Services", "heading": "Nombre d'établissements à but lucratif : par secteur d'activité", "category": "Services", "unit": "establishments"}, {"path": "size.1", "column": "size_1", "topic": "size", "key": "1", "label": "One person", "heading": "Nombre d'établissements à but lucratif : par classe d'emploi", "category": "un seul employé", "unit": "establishments"}, {"path": "size.2-3", "column": "size_2_3", "topic": "size", "key": "2-3", "label": "2 to 3 people", "heading": "Nombre d'établissements à but lucratif : par classe d'emploi", "category": "2 à 3", "unit": "establishments"}, {"path": "size.4-9", "column": "size_4_9", "topic": "size", "key": "4-9", "label": "4 to 9 people", "heading": "Nombre d'établissements à but lucratif : par classe d'emploi", "category": "4 à 9", "unit": "establishments"}, {"path": "size.10-49", "column": "size_10_49", "topic": "size", "key": "10-49", "label": "10 to 49 people", "heading": "Nombre d'établissements à but lucratif : par classe d'emploi", "category": "10 à 49", "unit": "establishments"}, {"path": "size.50+", "column": "size_50_plus", "topic": "size", "key": "50+", "label": "50 people and over", "heading": "Nombre d'établissements à but lucratif : par classe d'emploi", "category": "50 & +", "unit": "establishments"}, {"path": "founded.before1956", "column": "founded_before_1956", "topic": "founded", "key": "before1956", "label": "Before 1956", "heading": "Nombre d'établissements à but lucratif : par classe de date de création", "category": "Avant 1956", "unit": "establishments"}, {"path": "founded.1956-1980", "column": "founded_1956_1980", "topic": "founded", "key": "1956-1980", "label": "1956 to 1980", "heading": "Nombre d'établissements à but lucratif : par classe de date de création", "category": "1956-1980", "unit": "establishments"}, {"path": "founded.1981-1990", "column": "founded_1981_1990", "topic": "founded", "key": "1981-1990", "label": "1981 to 1990", "heading": "Nombre d'établissements à but lucratif : par classe de date de création", "category": "1981-1990", "unit": "establishments"}, {"path": "founded.1991-2000", "column": "founded_1991_2000", "topic": "founded", "key": "1991-2000", "label": "1991 to 2000", "heading": "Nombre d'établissements à but lucratif : par classe de date de création", "category": "1991-2000", "unit": "establishments"}, {"path": "founded.2001-2010", "column": "founded_2001_2010", "topic": "founded", "key": "2001-2010", "label": "2001 to 2010", "heading": "Nombre d'établissements à but lucratif : par classe de date de création", "category": "2001-2010", "unit": "establishments"}, {"path": "founded.2011-2019", "column": "founded_2011_2019", "topic": "founded", "key": "2011-2019", "label": "2011 to 2019", "heading": "Nombre d'établissements à but lucratif : par classe de date de création", "category": "2011-2019", "unit": "establishments"}, {"path": "founded.2020+", "column": "founded_2020_plus", "topic": "founded", "key": "2020+", "label": "2020 and later", "heading": "Nombre d'établissements à but lucratif : par classe de date de création", "category": "2020 et +", "unit": "establishments"}], "notes": {"counted": "Every establishment HCP's field teams found and put on the map during the 2024 census: a place of business, a public service, or an association in premises of its own.", "agriculture": "Farms are out. The workbook counts the establishments of every sector but agriculture.", "souks": "A weekly souk is a market that stands on one day of the week. It is counted on its own and is not part of the establishment total.", "jobs": "The jobs are the permanent ones the businesses hold, so seasonal and casual work is not in the figure.", "units": "The workbook counts the 6 cities with arrondissements by arrondissement rather than as one place. Each of those cities carries the sum of its own arrondissements, marked basis: arrondissement_sum, the way its 2014 population is. Every other figure is a row of HCP's."}}
|