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.
Files changed (29) hide show
  1. morocco_communes-1.8.0/.gitignore +14 -0
  2. morocco_communes-1.8.0/LICENSE +27 -0
  3. morocco_communes-1.8.0/PKG-INFO +84 -0
  4. morocco_communes-1.8.0/README.md +61 -0
  5. morocco_communes-1.8.0/build_data.py +104 -0
  6. morocco_communes-1.8.0/pyproject.toml +37 -0
  7. morocco_communes-1.8.0/src/morocco_communes/__init__.py +51 -0
  8. morocco_communes-1.8.0/src/morocco_communes/_data.py +178 -0
  9. morocco_communes-1.8.0/src/morocco_communes/_version.py +3 -0
  10. morocco_communes-1.8.0/src/morocco_communes/data/attributes/arrondissements.csv.gz +0 -0
  11. morocco_communes-1.8.0/src/morocco_communes/data/attributes/cercles.csv.gz +0 -0
  12. morocco_communes-1.8.0/src/morocco_communes/data/attributes/communes.csv.gz +0 -0
  13. morocco_communes-1.8.0/src/morocco_communes/data/attributes/provinces.csv.gz +0 -0
  14. morocco_communes-1.8.0/src/morocco_communes/data/attributes/regions.csv.gz +0 -0
  15. morocco_communes-1.8.0/src/morocco_communes/data/crosswalk/2014-2024.csv.gz +0 -0
  16. morocco_communes-1.8.0/src/morocco_communes/data/economy/establishments.csv.gz +0 -0
  17. morocco_communes-1.8.0/src/morocco_communes/data/fields/economy.json +1 -0
  18. morocco_communes-1.8.0/src/morocco_communes/data/fields/housing.json +1 -0
  19. morocco_communes-1.8.0/src/morocco_communes/data/fields/indicators.json +1 -0
  20. morocco_communes-1.8.0/src/morocco_communes/data/fields/indicators2014.json +1 -0
  21. morocco_communes-1.8.0/src/morocco_communes/data/housing/dwellings.csv.gz +0 -0
  22. morocco_communes-1.8.0/src/morocco_communes/data/indicators/households.csv.gz +0 -0
  23. morocco_communes-1.8.0/src/morocco_communes/data/indicators/people.csv.gz +0 -0
  24. morocco_communes-1.8.0/src/morocco_communes/data/indicators2014/households.csv.gz +0 -0
  25. morocco_communes-1.8.0/src/morocco_communes/data/indicators2014/people.csv.gz +0 -0
  26. morocco_communes-1.8.0/src/morocco_communes/data/sources.json +1 -0
  27. morocco_communes-1.8.0/src/morocco_communes/py.typed +0 -0
  28. morocco_communes-1.8.0/tests/python.test.ts +30 -0
  29. 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}"
@@ -0,0 +1,3 @@
1
+ """Generated by build.py. Do not edit."""
2
+
3
+ __version__ = "1.8.0"
@@ -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."}}