pycancensus 0.1.0__py3-none-any.whl
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.
- pycancensus/__init__.py +52 -0
- pycancensus/cache.py +187 -0
- pycancensus/cli.py +184 -0
- pycancensus/core.py +512 -0
- pycancensus/datasets.py +217 -0
- pycancensus/geometry.py +67 -0
- pycancensus/hierarchy.py +362 -0
- pycancensus/intersect_geometry.py +218 -0
- pycancensus/progress.py +247 -0
- pycancensus/regions.py +171 -0
- pycancensus/resilience.py +334 -0
- pycancensus/settings.py +221 -0
- pycancensus/utils.py +151 -0
- pycancensus/vectors.py +296 -0
- pycancensus-0.1.0.dist-info/METADATA +268 -0
- pycancensus-0.1.0.dist-info/RECORD +20 -0
- pycancensus-0.1.0.dist-info/WHEEL +5 -0
- pycancensus-0.1.0.dist-info/entry_points.txt +2 -0
- pycancensus-0.1.0.dist-info/licenses/LICENSE +21 -0
- pycancensus-0.1.0.dist-info/top_level.txt +1 -0
pycancensus/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pycancensus: Access, retrieve, and work with Canadian Census data and geography.
|
|
3
|
+
|
|
4
|
+
This package provides integrated, convenient, and uniform access to Canadian
|
|
5
|
+
Census data and geography retrieved using the CensusMapper API.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__author__ = "Dmitry Shkolnik"
|
|
10
|
+
__email__ = "shkolnikd@gmail.com"
|
|
11
|
+
|
|
12
|
+
from .core import get_census
|
|
13
|
+
from .regions import list_census_regions, search_census_regions
|
|
14
|
+
from .vectors import list_census_vectors, search_census_vectors, label_vectors
|
|
15
|
+
from .datasets import list_census_datasets, dataset_attribution
|
|
16
|
+
from .settings import (
|
|
17
|
+
set_api_key,
|
|
18
|
+
get_api_key,
|
|
19
|
+
set_cache_path,
|
|
20
|
+
get_cache_path,
|
|
21
|
+
remove_api_key,
|
|
22
|
+
show_api_key,
|
|
23
|
+
)
|
|
24
|
+
from .geometry import get_census_geometry
|
|
25
|
+
from .cache import list_cache, remove_from_cache, clear_cache
|
|
26
|
+
from .hierarchy import parent_census_vectors, child_census_vectors, find_census_vectors
|
|
27
|
+
from .intersect_geometry import get_intersecting_geometries
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"get_census",
|
|
31
|
+
"list_census_regions",
|
|
32
|
+
"search_census_regions",
|
|
33
|
+
"list_census_vectors",
|
|
34
|
+
"search_census_vectors",
|
|
35
|
+
"label_vectors",
|
|
36
|
+
"list_census_datasets",
|
|
37
|
+
"dataset_attribution",
|
|
38
|
+
"set_api_key",
|
|
39
|
+
"get_api_key",
|
|
40
|
+
"remove_api_key",
|
|
41
|
+
"show_api_key",
|
|
42
|
+
"set_cache_path",
|
|
43
|
+
"get_cache_path",
|
|
44
|
+
"get_census_geometry",
|
|
45
|
+
"list_cache",
|
|
46
|
+
"remove_from_cache",
|
|
47
|
+
"clear_cache",
|
|
48
|
+
"parent_census_vectors",
|
|
49
|
+
"child_census_vectors",
|
|
50
|
+
"find_census_vectors",
|
|
51
|
+
"get_intersecting_geometries",
|
|
52
|
+
]
|
pycancensus/cache.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Caching functionality for pycancensus.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import pickle
|
|
7
|
+
import hashlib
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Optional, List
|
|
10
|
+
import pandas as pd
|
|
11
|
+
import geopandas as gpd
|
|
12
|
+
|
|
13
|
+
from .settings import get_cache_path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_cached_data(cache_key: str) -> Optional[Any]:
|
|
17
|
+
"""
|
|
18
|
+
Retrieve data from cache if it exists.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
cache_key : str
|
|
23
|
+
Unique identifier for the cached data.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
Any or None
|
|
28
|
+
Cached data if found, None otherwise.
|
|
29
|
+
"""
|
|
30
|
+
cache_path = Path(get_cache_path())
|
|
31
|
+
cache_file = cache_path / f"{cache_key}.pkl"
|
|
32
|
+
|
|
33
|
+
if cache_file.exists():
|
|
34
|
+
try:
|
|
35
|
+
with open(cache_file, "rb") as f:
|
|
36
|
+
return pickle.load(f)
|
|
37
|
+
except Exception:
|
|
38
|
+
# If cache file is corrupted, remove it
|
|
39
|
+
cache_file.unlink(missing_ok=True)
|
|
40
|
+
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def cache_data(cache_key: str, data: Any) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Cache data to disk.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
cache_key : str
|
|
51
|
+
Unique identifier for the data.
|
|
52
|
+
data : Any
|
|
53
|
+
Data to cache.
|
|
54
|
+
"""
|
|
55
|
+
cache_path = Path(get_cache_path())
|
|
56
|
+
cache_path.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
|
|
58
|
+
cache_file = cache_path / f"{cache_key}.pkl"
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
with open(cache_file, "wb") as f:
|
|
62
|
+
pickle.dump(data, f)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"Warning: Failed to cache data: {e}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def list_cache() -> pd.DataFrame:
|
|
68
|
+
"""
|
|
69
|
+
List all cached data files.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
pd.DataFrame
|
|
74
|
+
DataFrame with information about cached files including:
|
|
75
|
+
- cache_key: The cache key
|
|
76
|
+
- file_path: Full path to cached file
|
|
77
|
+
- size_mb: File size in MB
|
|
78
|
+
- created: Creation timestamp
|
|
79
|
+
- modified: Last modification timestamp
|
|
80
|
+
|
|
81
|
+
Examples
|
|
82
|
+
--------
|
|
83
|
+
>>> import pycancensus as pc
|
|
84
|
+
>>> cache_list = pc.list_cache()
|
|
85
|
+
>>> print(cache_list)
|
|
86
|
+
"""
|
|
87
|
+
cache_path = Path(get_cache_path())
|
|
88
|
+
|
|
89
|
+
if not cache_path.exists():
|
|
90
|
+
return pd.DataFrame(
|
|
91
|
+
columns=["cache_key", "file_path", "size_mb", "created", "modified"]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
cache_files = []
|
|
95
|
+
|
|
96
|
+
for cache_file in cache_path.glob("*.pkl"):
|
|
97
|
+
try:
|
|
98
|
+
stat = cache_file.stat()
|
|
99
|
+
cache_files.append(
|
|
100
|
+
{
|
|
101
|
+
"cache_key": cache_file.stem,
|
|
102
|
+
"file_path": str(cache_file),
|
|
103
|
+
"size_mb": round(stat.st_size / (1024 * 1024), 2),
|
|
104
|
+
"created": pd.Timestamp.fromtimestamp(stat.st_ctime),
|
|
105
|
+
"modified": pd.Timestamp.fromtimestamp(stat.st_mtime),
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
except Exception:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
return pd.DataFrame(cache_files)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def remove_from_cache(
|
|
115
|
+
cache_keys: Optional[List[str]] = None, all_cache: bool = False
|
|
116
|
+
) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Remove items from cache.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
cache_keys : list of str, optional
|
|
123
|
+
Specific cache keys to remove. If None and all_cache=False,
|
|
124
|
+
does nothing.
|
|
125
|
+
all_cache : bool, default False
|
|
126
|
+
If True, removes all cached data.
|
|
127
|
+
|
|
128
|
+
Examples
|
|
129
|
+
--------
|
|
130
|
+
>>> import pycancensus as pc
|
|
131
|
+
>>> # Remove specific cache entries
|
|
132
|
+
>>> pc.remove_from_cache(["regions_CA16", "vectors_CA16"])
|
|
133
|
+
>>>
|
|
134
|
+
>>> # Remove all cache (use with caution!)
|
|
135
|
+
>>> pc.remove_from_cache(all_cache=True)
|
|
136
|
+
"""
|
|
137
|
+
cache_path = Path(get_cache_path())
|
|
138
|
+
|
|
139
|
+
if not cache_path.exists():
|
|
140
|
+
print("No cache directory found.")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
removed_count = 0
|
|
144
|
+
|
|
145
|
+
if all_cache:
|
|
146
|
+
# Remove all .pkl files
|
|
147
|
+
for cache_file in cache_path.glob("*.pkl"):
|
|
148
|
+
try:
|
|
149
|
+
cache_file.unlink()
|
|
150
|
+
removed_count += 1
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print(f"Warning: Failed to remove {cache_file}: {e}")
|
|
153
|
+
|
|
154
|
+
print(f"Removed {removed_count} cached files.")
|
|
155
|
+
|
|
156
|
+
elif cache_keys:
|
|
157
|
+
# Remove specific cache keys
|
|
158
|
+
for cache_key in cache_keys:
|
|
159
|
+
cache_file = cache_path / f"{cache_key}.pkl"
|
|
160
|
+
if cache_file.exists():
|
|
161
|
+
try:
|
|
162
|
+
cache_file.unlink()
|
|
163
|
+
removed_count += 1
|
|
164
|
+
print(f"Removed cache: {cache_key}")
|
|
165
|
+
except Exception as e:
|
|
166
|
+
print(f"Warning: Failed to remove {cache_key}: {e}")
|
|
167
|
+
else:
|
|
168
|
+
print(f"Cache key not found: {cache_key}")
|
|
169
|
+
|
|
170
|
+
if removed_count > 0:
|
|
171
|
+
print(f"Removed {removed_count} cached files.")
|
|
172
|
+
else:
|
|
173
|
+
print("No cache keys specified and all_cache=False. Nothing to remove.")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def clear_cache() -> None:
|
|
177
|
+
"""
|
|
178
|
+
Clear all cached data.
|
|
179
|
+
|
|
180
|
+
This is an alias for remove_from_cache(all_cache=True).
|
|
181
|
+
|
|
182
|
+
Examples
|
|
183
|
+
--------
|
|
184
|
+
>>> import pycancensus as pc
|
|
185
|
+
>>> pc.clear_cache()
|
|
186
|
+
"""
|
|
187
|
+
remove_from_cache(all_cache=True)
|
pycancensus/cli.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Command-line interface for pycancensus.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import pycancensus as pc
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
"""Main CLI entry point."""
|
|
16
|
+
parser = argparse.ArgumentParser(
|
|
17
|
+
description="Access Canadian Census data through the CensusMapper API",
|
|
18
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
19
|
+
epilog="""
|
|
20
|
+
Examples:
|
|
21
|
+
pycancensus datasets # List available datasets
|
|
22
|
+
pycancensus regions CA16 # List regions for 2016 Census
|
|
23
|
+
pycancensus vectors CA16 # List vectors for 2016 Census
|
|
24
|
+
pycancensus search-regions Vancouver CA16 # Search for Vancouver regions
|
|
25
|
+
pycancensus search-vectors income CA16 # Search for income vectors
|
|
26
|
+
|
|
27
|
+
Set API key with environment variable: export CANCENSUS_API_KEY=your_key_here
|
|
28
|
+
Or get a free key at: https://censusmapper.ca/users/sign_up
|
|
29
|
+
""",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--api-key",
|
|
34
|
+
help="CensusMapper API key (or set CANCENSUS_API_KEY environment variable)",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument("--cache-path", help="Path for caching downloaded data")
|
|
37
|
+
parser.add_argument("--no-cache", action="store_true", help="Disable caching")
|
|
38
|
+
parser.add_argument("--quiet", action="store_true", help="Suppress output messages")
|
|
39
|
+
|
|
40
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
41
|
+
|
|
42
|
+
# Datasets command
|
|
43
|
+
datasets_parser = subparsers.add_parser("datasets", help="List available datasets")
|
|
44
|
+
|
|
45
|
+
# Regions command
|
|
46
|
+
regions_parser = subparsers.add_parser("regions", help="List regions for a dataset")
|
|
47
|
+
regions_parser.add_argument("dataset", help="Dataset identifier (e.g., CA16)")
|
|
48
|
+
|
|
49
|
+
# Vectors command
|
|
50
|
+
vectors_parser = subparsers.add_parser("vectors", help="List vectors for a dataset")
|
|
51
|
+
vectors_parser.add_argument("dataset", help="Dataset identifier (e.g., CA16)")
|
|
52
|
+
|
|
53
|
+
# Search regions command
|
|
54
|
+
search_regions_parser = subparsers.add_parser(
|
|
55
|
+
"search-regions", help="Search for regions"
|
|
56
|
+
)
|
|
57
|
+
search_regions_parser.add_argument("term", help="Search term")
|
|
58
|
+
search_regions_parser.add_argument(
|
|
59
|
+
"dataset", help="Dataset identifier (e.g., CA16)"
|
|
60
|
+
)
|
|
61
|
+
search_regions_parser.add_argument("--level", help="Filter by aggregation level")
|
|
62
|
+
|
|
63
|
+
# Search vectors command
|
|
64
|
+
search_vectors_parser = subparsers.add_parser(
|
|
65
|
+
"search-vectors", help="Search for vectors"
|
|
66
|
+
)
|
|
67
|
+
search_vectors_parser.add_argument("term", help="Search term")
|
|
68
|
+
search_vectors_parser.add_argument(
|
|
69
|
+
"dataset", help="Dataset identifier (e.g., CA16)"
|
|
70
|
+
)
|
|
71
|
+
search_vectors_parser.add_argument("--type", help="Filter by vector type")
|
|
72
|
+
|
|
73
|
+
# Get data command
|
|
74
|
+
get_data_parser = subparsers.add_parser("get-data", help="Get census data")
|
|
75
|
+
get_data_parser.add_argument("dataset", help="Dataset identifier (e.g., CA16)")
|
|
76
|
+
get_data_parser.add_argument(
|
|
77
|
+
"--regions", required=True, help='Regions as JSON (e.g., \'{"CMA": "59933"}\')'
|
|
78
|
+
)
|
|
79
|
+
get_data_parser.add_argument("--vectors", help="Comma-separated list of vector IDs")
|
|
80
|
+
get_data_parser.add_argument("--level", default="Regions", help="Aggregation level")
|
|
81
|
+
get_data_parser.add_argument("--geo", action="store_true", help="Include geometry")
|
|
82
|
+
get_data_parser.add_argument("--output", help="Output file path (CSV or GeoJSON)")
|
|
83
|
+
|
|
84
|
+
args = parser.parse_args()
|
|
85
|
+
|
|
86
|
+
# Set up configuration
|
|
87
|
+
if args.api_key:
|
|
88
|
+
pc.set_api_key(args.api_key)
|
|
89
|
+
|
|
90
|
+
if args.cache_path:
|
|
91
|
+
pc.set_cache_path(args.cache_path)
|
|
92
|
+
|
|
93
|
+
use_cache = not args.no_cache
|
|
94
|
+
quiet = args.quiet
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
if args.command == "datasets":
|
|
98
|
+
df = pc.list_census_datasets(use_cache=use_cache, quiet=quiet)
|
|
99
|
+
print(df.to_string(index=False))
|
|
100
|
+
|
|
101
|
+
elif args.command == "regions":
|
|
102
|
+
df = pc.list_census_regions(args.dataset, use_cache=use_cache, quiet=quiet)
|
|
103
|
+
print(df.to_string(index=False))
|
|
104
|
+
|
|
105
|
+
elif args.command == "vectors":
|
|
106
|
+
df = pc.list_census_vectors(args.dataset, use_cache=use_cache, quiet=quiet)
|
|
107
|
+
print(df.to_string(index=False))
|
|
108
|
+
|
|
109
|
+
elif args.command == "search-regions":
|
|
110
|
+
df = pc.search_census_regions(
|
|
111
|
+
args.term,
|
|
112
|
+
args.dataset,
|
|
113
|
+
level=args.level,
|
|
114
|
+
use_cache=use_cache,
|
|
115
|
+
quiet=quiet,
|
|
116
|
+
)
|
|
117
|
+
print(df.to_string(index=False))
|
|
118
|
+
|
|
119
|
+
elif args.command == "search-vectors":
|
|
120
|
+
df = pc.search_census_vectors(
|
|
121
|
+
args.term,
|
|
122
|
+
args.dataset,
|
|
123
|
+
type_filter=args.type,
|
|
124
|
+
use_cache=use_cache,
|
|
125
|
+
quiet=quiet,
|
|
126
|
+
)
|
|
127
|
+
print(df.to_string(index=False))
|
|
128
|
+
|
|
129
|
+
elif args.command == "get-data":
|
|
130
|
+
# Parse regions JSON
|
|
131
|
+
try:
|
|
132
|
+
regions = json.loads(args.regions)
|
|
133
|
+
except json.JSONDecodeError:
|
|
134
|
+
print("Error: Invalid JSON format for regions", file=sys.stderr)
|
|
135
|
+
return 1
|
|
136
|
+
|
|
137
|
+
# Parse vectors
|
|
138
|
+
vectors = None
|
|
139
|
+
if args.vectors:
|
|
140
|
+
vectors = [v.strip() for v in args.vectors.split(",")]
|
|
141
|
+
|
|
142
|
+
# Get data
|
|
143
|
+
geo_format = "geopandas" if args.geo else None
|
|
144
|
+
|
|
145
|
+
df = pc.get_census(
|
|
146
|
+
dataset=args.dataset,
|
|
147
|
+
regions=regions,
|
|
148
|
+
vectors=vectors,
|
|
149
|
+
level=args.level,
|
|
150
|
+
geo_format=geo_format,
|
|
151
|
+
use_cache=use_cache,
|
|
152
|
+
quiet=quiet,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Output data
|
|
156
|
+
if args.output:
|
|
157
|
+
if args.output.endswith(".geojson") and hasattr(df, "to_file"):
|
|
158
|
+
df.to_file(args.output, driver="GeoJSON")
|
|
159
|
+
if not quiet:
|
|
160
|
+
print(f"Data saved to {args.output}")
|
|
161
|
+
else:
|
|
162
|
+
df.to_csv(args.output, index=False)
|
|
163
|
+
if not quiet:
|
|
164
|
+
print(f"Data saved to {args.output}")
|
|
165
|
+
else:
|
|
166
|
+
print(df.to_string(index=False))
|
|
167
|
+
|
|
168
|
+
else:
|
|
169
|
+
parser.print_help()
|
|
170
|
+
return 1
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
174
|
+
if not quiet:
|
|
175
|
+
import traceback
|
|
176
|
+
|
|
177
|
+
traceback.print_exc()
|
|
178
|
+
return 1
|
|
179
|
+
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
sys.exit(main())
|