diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a88ddbe --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: Lint (ruff) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: python -m pip install ruff + - run: ruff check src tests + - run: ruff format --check src tests + + test: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + - name: Install + run: python -m pip install -e ".[dev,osm,contour]" + - name: Test + run: pytest --cov --cov-report=term-missing + + build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: python -m pip install build twine + - run: python -m build + - run: twine check dist/* + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..9660dd1 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,45 @@ +name: Docs + +on: + push: + branches: [master] + paths: ["docs/**", "mkdocs.yml", "src/**", "CHANGELOG.md", "CONTRIBUTING.md", ".github/workflows/docs.yml"] + pull_request: + paths: ["docs/**", "mkdocs.yml", "src/**", "CHANGELOG.md", "CONTRIBUTING.md", ".github/workflows/docs.yml"] + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + name: Build (strict) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + - run: python -m pip install -e ".[docs]" + - run: mkdocs build --strict + + deploy: + name: Deploy to GitHub Pages + needs: build + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + - run: python -m pip install -e ".[docs]" + - name: Publish to the gh-pages branch + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + mkdocs gh-deploy --force --no-history diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..ff46db3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,47 @@ +name: Publish to PyPI + +# Publishes when a version tag (v1.2.3) is pushed. Uses PyPI "trusted +# publishing" (OpenID Connect), so no API token is stored in the repository: +# register this workflow once at https://pypi.org/manage/account/publishing/ +# (owner: adaj, repository: predspot, workflow: publish.yml, environment: pypi). + +on: + push: + tags: ["v[0-9]+.[0-9]+.[0-9]+*"] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Check that the tag matches the package version + run: | + python -m pip install -e . + PKG_VERSION="v$(python -c 'import predspot; print(predspot.__version__)')" + echo "tag=${GITHUB_REF_NAME} package=${PKG_VERSION}" + test "${GITHUB_REF_NAME}" = "${PKG_VERSION}" + - run: python -m pip install build twine + - run: python -m build + - run: twine check dist/* + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/predspot + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index 1c0482f..7591d61 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,9 @@ build/ dist/ *.egg-info/ +.coverage +htmlcov/ +.ruff_cache/ +site/ +examples/cache/ +.cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e400e6e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,65 @@ +# Changelog + +All notable changes to Predspot are documented here. The format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses +[Semantic Versioning](https://semver.org/). + +## [Unreleased] + +## [0.2.0] - 2026-09 + +Revival release: the code base now targets Python 3.10+ with current +versions of pandas (>= 2.2), GeoPandas (>= 1.0), scikit-learn and statsmodels. + +### Added +- `crime_mapping.get_city_shape("Natal, RN, Brazil")` — thin wrapper around + `osmnx.geocode_to_gdf` returning the raw city shape. +- `examples/natal.ipynb`: an executed end-to-end walkthrough on Natal with + synthetic data, also rendered in the documentation. +- `PredictionPipeline.evaluate` accepts a list of scorings and returns a + DataFrame (one CV pass for all metrics). +- README section explaining the framework (thesis, Chapter 3, Figures 7-12); + the README is now the documentation home page. +- `crime_mapping.load_study_area("City, Country")` fetches a study area + boundary from OpenStreetMap via `osmnx` (`pip install predspot[osm]`). +- `synthetic.generate_crimes` generates synthetic events inside any study + area: Gaussian hotspots plus uniform background, with trend, annual cycle, + day-of-week and hour-of-day patterns; reproducible with `seed`. +- `QuadratCount` mapping (event counts per cell) as a first-class alternative + to `KDE`, usable with hexagonal (`create_gridhexagonal`) and square + (`create_gridsquares`) grids inside `PredictionPipeline`. +- `pipeline.build_default_pipeline` and reproducible + `pipeline.generate_testdata(..., seed=...)`. +- `PredictionPipeline.features`, `.next_time` and `random_state`. +- Test suite (pytest) and continuous integration for Python 3.10-3.13. +- Documentation rebuilt with MkDocs (Material + mkdocstrings), deployed + automatically to GitHub Pages; replaces the Sphinx site. +- `pyproject.toml` packaging (src layout) and automated PyPI publishing. + +### Changed +- `tfreq` is optional in the feature classes (inferred from the series). +- Debug `print`s replaced with the `logging` module (`predspot` logger); the + `debug=` arguments were removed. +- Wrapper estimators expose their inner estimator as `.estimator` + (previously `._estimator`). +- `Dataset` no longer modifies the input DataFrame and requires the study + area to have a CRS. +- Grid centroids are computed in a projected CRS; grids accept study areas in + any CRS. +- `geojsoncontour` is an optional dependency (`pip install predspot[contour]`). + +### Removed +- Sphinx documentation sources and the committed HTML build. +- `QuadratCount2`, `KGrid` and the hard dependencies on `descartes`, + `contextily` and `rtree`. + +### Fixed +- Compatibility with pandas 2/3 (`'ME'` offsets, `DataFrame.append`, + positional `Series` indexing), GeoPandas 1.x (`sjoin(predicate=)`, CRS + strings, `gpd.datasets`) and scikit-learn 1.x (`FeatureUnion` internals). +- `Seasonality`/`Trend` never called `STL(...).fit()`. +- `FeatureScaling` had no `fit`, so scalers inside a `Pipeline` were never fitted. + +## [0.1.3] - 2020 + +Original master's thesis release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..06ae5fb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing + +Thanks for your interest in Predspot! Issues and pull requests are welcome. + +## Development setup + +```bash +git clone https://github.com/adaj/predspot.git +cd predspot +python -m venv .venv && source .venv/bin/activate +pip install -e ".[dev,osm,contour]" +``` + +## Checks + +```bash +ruff check src tests # lint +ruff format src tests # format +pytest # tests (~10 s) +PREDSPOT_NETWORK_TESTS=1 pytest tests/test_load_study_area.py # also query OpenStreetMap +``` + +CI runs the same checks on Python 3.10 to 3.13 for every pull request. + +## Documentation + +The site is built with [MkDocs](https://www.mkdocs.org/) and +[Material](https://squidfunk.github.io/mkdocs-material/); API pages come from the +docstrings via mkdocstrings. + +```bash +pip install -e ".[docs]" +mkdocs serve # live preview at http://127.0.0.1:8000 +mkdocs build --strict # what CI runs +``` + +Pushing to `master` deploys the site to GitHub Pages automatically. The home +page is generated from `README.md` (see `docs/hooks/readme.py`), and the +example notebook is rendered from `examples/natal.ipynb`; regenerate and +re-execute it with: + +```bash +python examples/build_natal_notebook.py +jupyter nbconvert --to notebook --execute --inplace examples/natal.ipynb +``` + +## Releasing + +1. Bump `__version__` in `src/predspot/__init__.py` and update `CHANGELOG.md`. +2. Merge to `master`, then tag and push: `git tag v0.2.0 && git push origin v0.2.0`. +3. The `Publish to PyPI` workflow builds the distribution and uploads it via + PyPI trusted publishing. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..9da02ae --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE README.md CHANGELOG.md CONTRIBUTING.md +recursive-include tests *.py +prune docs diff --git a/README.md b/README.md index 5ca6949..a341d08 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,44 @@ # Predspot +[](https://github.com/adaj/predspot/actions/workflows/ci.yml) +[](https://adaj.github.io/predspot/) +[](https://pypi.org/project/predspot/) +[](https://pypi.org/project/predspot/) +[](https://github.com/adaj/predspot/blob/master/LICENSE) + ## Overview 📖 -Predspot is a Python library for spatio-temporal crime prediction and hotspot detection. It combines machine learning techniques with spatial analysis to help predict and visualize crime patterns across time and space. +Predspot is a Python library for spatio-temporal crime prediction and hotspot +detection. It turns a table of georeferenced, timestamped crime events into a +grid of *places* and a sequence of *periods*, builds time series features for +every place and trains a scikit-learn model to forecast where the next period's +hotspots will be. Key features: -- Spatial and temporal crime mapping -- Feature engineering for time series data -- Machine learning-based prediction pipeline -- Crime hotspot detection using Kernel Density Estimation -- Visualization tools for crime patterns -## Status 🚧 +- Spatio-temporal crime mapping: kernel density estimation (KDE) on point grids, + or event counts on hexagonal and square grids, at daily, weekly or monthly resolution +- Time series feature engineering: lagged autoregressive, difference, seasonal and + trend features (STL decomposition) +- A prediction pipeline that accepts any scikit-learn regressor, with time series + cross-validation and recursive multi-step forecasts +- Study areas fetched from OpenStreetMap by name +- A synthetic crime generator (hotspots, trend, annual, weekly and hourly patterns) + to try everything without real data -Predspot started as part of a master's thesis (2018-2019) and is being revived -and modernised. The code base now targets Python 3.10+ with current versions of -pandas (>= 2.2), GeoPandas (>= 1.0), scikit-learn and statsmodels, and is -covered by a test suite. It remains research software: use it as a reference -implementation and adapt it to your own data. +Full documentation, with a quickstart, a user guide and the API reference, lives at +**https://adaj.github.io/predspot/**. A complete, executed walkthrough for the city +of Natal is in [`examples/natal.ipynb`](https://github.com/adaj/predspot/blob/master/examples/natal.ipynb). ## How to use? 🚀 -> Documentation under construction. +Install from PyPI (Python 3.10 or newer): + +```bash +pip install predspot # core +pip install "predspot[osm]" # + study areas from OpenStreetMap (osmnx) +pip install "predspot[contour]" # + GeoJSON contour export (geojsoncontour) +``` Basic usage example: @@ -29,15 +46,14 @@ Basic usage example: from predspot import Dataset, PredictionPipeline from predspot.crime_mapping import KDE, create_gridpoints from predspot.feature_engineering import Seasonality, Trend, Diff - from predspot.utilities import PandasFeatureUnion from sklearn.ensemble import RandomForestRegressor -# Load and prepare data: crimes_df needs `tag`, `t`, `lon`, `lat` columns and +# crimes_df needs `tag`, `t`, `lon`, `lat` columns; # study_area_gdf is a GeoDataFrame with the boundary of the study area dataset = Dataset(crimes_df, study_area_gdf) -# Create prediction pipeline (monthly KDE on a 1 km point grid) +# Monthly KDE on a 1 km point grid, lag features, random forest pipeline = PredictionPipeline( mapping=KDE(tfreq='M', grid=create_gridpoints(study_area_gdf, resolution=1)), fextraction=PandasFeatureUnion([ @@ -62,6 +78,23 @@ from predspot.crime_mapping import QuadratCount, create_gridhexagonal mapping = QuadratCount(tfreq='W', grid=create_gridhexagonal(study_area_gdf, resolution=1)) ``` +### Study area from OpenStreetMap and synthetic data + +You do not need real data to try Predspot. Fetch a city boundary from +OpenStreetMap (`pip install "predspot[osm]"`) and generate synthetic events +with spatial hotspots and realistic temporal patterns (trend, annual cycle, +day-of-week and hour-of-day profiles): + +```python +from predspot import Dataset, get_city_shape, generate_crimes + +city = get_city_shape("Natal, RN, Brazil") +crimes = generate_crimes(city, n_events=5000, n_hotspots=4, + start="2019-01-01", end="2020-12-31", seed=0) +dataset = Dataset(crimes, city) +dataset.plot() +``` + Or run the default pipeline in one call: ```python @@ -72,72 +105,127 @@ predictions, pipeline = run_prediction_pipeline(crimes, study_area, grid_resolut print(pipeline.evaluate('r2', cv=3)) ``` - -## Development ⚡ - -Predspot has four main modules: - -`dataset_preparation`: Module for preparing and managing crime datasets and study areas. - -`crime_mapping`: Module for spatial and temporal crime mapping: point, hexagonal and square grids, KDE-based density surfaces and per-cell counts (`QuadratCount`). - -`feature_engineering`: Module for time series feature engineering, including seasonality, trend, and difference features. - -`ml_modelling`: Module that implements the prediction pipeline and model evaluation. - -### Installation steps 🛠️ - -Predspot requires Python 3.10 or newer. - -```bash -git clone https://github.com/adaj/predspot.git -cd predspot -pip install . -``` - -Core dependencies (installed automatically): pandas, geopandas, shapely, -numpy, scipy, scikit-learn, statsmodels and matplotlib. The optional -`geojsoncontour` package enables `predspot.utilities.contour_geojson`. - -### Tests 🧪 - -```bash -pip install pytest -pytest -``` - -### Input Data Format 📊 +### Input data format 📊 The crime data should be a pandas DataFrame with the following required columns: -- `tag`: Crime type -- `t`: Timestamp -- `lon`: Longitude -- `lat`: Latitude -The study area should be a GeoDataFrame defining the geographical boundaries of interest. +- `tag`: crime type +- `t`: timestamp +- `lon`: longitude (WGS84 degrees) +- `lat`: latitude (WGS84 degrees) + +The study area should be a GeoDataFrame (with a CRS) defining the boundaries +of interest. + +## The Predspot framework 🧭 + +Predspot implements the framework described in Chapter 3 of the master's +thesis [*Predspot: predicting crime hotspots with machine learning*](https://repositorio.ufrn.br/server/api/core/bitstreams/3655b8e1-2f32-4ce9-af9c-0e6b64d7af84/content) +(Araújo Jr., 2019). The framework is split into two phases, mirroring the +training and prediction steps of a machine learning system: **model +selection**, where a model is trained, evaluated and saved, and **prediction +service**, where that model is used operationally, period after period. The +figures below are reproduced from the thesis. + +### Model selection + +**1. Dataset preparation** *(Figure 7)* — everything starts from three inputs: +a crime database, the city shape and, optionally, auxiliary Points of Interest +(PoI) from OpenStreetMap. + +






-"""
-Crime Mapping Module
-
-This module provides functionality for spatial and temporal crime mapping analysis.
-It includes utilities for creating grid points, hexagonal grids, and implementing
-kernel density estimation for crime hotspot detection.
-"""
-
-__author__ = 'Adelson Araujo'
-from abc import ABC, abstractmethod
-import math
-import numpy as np
-import pandas as pd
-import geopandas as gpd
-from sklearn.base import BaseEstimator, TransformerMixin
-from sklearn.cluster import KMeans
-from shapely.geometry import Point, Polygon, MultiPoint
-from scipy.stats import gaussian_kde
-
-pd.options.mode.chained_assignment = None
-
-
-
-[docs]
-def create_gridpoints(bbox, resolution, return_coords=False, debug=False):
- """
- Create a grid of points within a given bounding box.
-
- Args:
- bbox (GeoDataFrame): Bounding box as a GeoDataFrame
- resolution (float): Grid cell size in kilometers
- return_coords (bool): If True, returns additional coordinate arrays
- debug (bool): Enable debug printing
-
- Returns:
- GeoDataFrame or tuple: Grid points as GeoDataFrame, optionally with coordinate arrays
- """
- if debug:
- print(f"Creating grid with resolution: {resolution}km", flush=True)
-
- assert resolution > 0, \
- "Invalid resolution."
- assert isinstance(bbox, gpd.GeoDataFrame), \
- 'bbox must be geopandas GeoDataFrame.'
- bounds = bbox.bounds
- b_s, b_w = bounds.min().values[1], bounds.min().values[0]
- b_n, b_e = bounds.max().values[3], bounds.max().values[2]
- nlon = int(np.ceil((b_e-b_w) / (resolution/111.32)))
- nlat = int(np.ceil((b_n-b_s) / (resolution/110.57)))
- lonv, latv = np.meshgrid(np.linspace(b_w, b_e, nlon), np.linspace(b_s, b_n, nlat))
- gridpoints = pd.DataFrame(np.vstack([lonv.ravel(), latv.ravel()]).T,
- columns=['lon', 'lat'])
- gridpoints['geometry'] = gridpoints.apply(lambda x: Point([x['lon'], x['lat']]),
- axis=1)
- gridpoints = gpd.GeoDataFrame(gridpoints)
- gridpoints.crs = {'init': 'epsg:4326'}
- gridpoints = gridpoints.to_crs(bbox.crs)
- grid_ix = gpd.sjoin(gridpoints, bbox, op='intersects').index.unique()
- if len(grid_ix) == 0:
- raise Exception("resolution too big/coarse. No cells were generated.")
- # elif len(grid_ix) / bbox.area.sum() > 10:
- # warnings.warn('resolution too fine/small. As consequence, your program' \
- # + 'may run very slowly.')
- gridpoints = gridpoints.loc[grid_ix]
- gridpoints.index.name = 'places'
- if return_coords:
- return gridpoints, lonv, latv
- return gridpoints
-
-
-
-
-[docs]
-def create_hexagon(l, x, y):
- """
- Create a hexagonal polygon.
-
- Args:
- l (float): Length of hexagon side
- x (float): X-coordinate of center
- y (float): Y-coordinate of center
-
- Returns:
- Polygon: Hexagonal polygon
- """
- c = [[x + math.cos(math.radians(angle)) * l, y + math.sin(math.radians(angle)) * l] for angle in range(0, 360, 60)]
- return Polygon(c)
-
-
-
-
-[docs]
-def create_gridhexagonal(bbox, resolution):
- assert resolution > 0, \
- "Invalid resolution."
- resolution = ((resolution)**2 * (2/(3*(3**0.5)))) ** 0.5 # normalize resolution to have the same area as if it was a square
- assert isinstance(bbox, gpd.GeoDataFrame), \
- 'bbox must be geopandas GeoDataFrame.'
- bbox_ = list(bbox.bounds.min().values[:2]) + list(bbox.bounds.max().values[-2:])
- x_min = min(bbox_[0], bbox_[2])
- x_max = max(bbox_[0], bbox_[2])
- y_min = min(bbox_[1], bbox_[3])
- y_max = max(bbox_[1], bbox_[3])
- grid = []
- resolution = resolution/110.6
- v_step = math.sqrt(3) * resolution
- h_step = 1.5 * resolution
- h_skip = math.ceil(x_min / h_step) - 1
- h_start = h_skip * h_step
- v_skip = math.ceil(y_min / v_step) - 1
- v_start = v_skip * v_step
- h_end = x_max + h_step
- v_end = y_max + v_step
- if v_start - (v_step / 2.0) < y_min:
- v_start_array = [v_start + (v_step / 2.0), v_start]
- else:
- v_start_array = [v_start - (v_step / 2.0), v_start]
- v_start_idx = int(abs(h_skip) % 2)
- c_x = h_start
- c_y = v_start_array[v_start_idx]
- v_start_idx = (v_start_idx + 1) % 2
- while c_x < h_end:
- while c_y < v_end:
- grid.append(create_hexagon(resolution, c_x, c_y))
- c_y += v_step
- c_x += h_step
- c_y = v_start_array[v_start_idx]
- v_start_idx = (v_start_idx + 1) % 2
- grid = gpd.GeoDataFrame(geometry=grid).reset_index()
- grid.crs = {'init': 'epsg:4326'}
- grid = grid.rename(columns={'index':'places'}).set_index('places')
- if isinstance(bbox, gpd.GeoDataFrame):
- grid = gpd.sjoin(grid, bbox, op='intersects')[grid.columns].drop_duplicates()
- grid = grid.to_crs(bbox.crs)
- grid['lon'] = grid['geometry'].centroid.x
- grid['lat'] = grid['geometry'].centroid.y
- return grid
-
-
-
-
-[docs]
-def create_gridsquares(city_shape, resolution=1):
- """It constructs a grid of square cells.
-
- Parameters
- ----------
- city_shape : GeoDataFrame.
- Corresponds to the boundary geometry in which the grid will be formed.
-
- resolution : float, default is 1.
- Space between the square cells.
- """
- x0 = city_shape.bounds.min().values[0]
- xf = city_shape.bounds.max().values[2]
- y0 = city_shape.bounds.min().values[1]
- yf = city_shape.bounds.max().values[3]
- n_y = int((yf-y0)/(resolution/110.57))
- n_x = int((xf-x0)/(resolution/111.32))
- grid = {}
- c = 0
- for i in range(n_x):
- for j in range(n_y):
- grid[c] = {'geometry':Polygon([[x0,y0],
- [x0+(resolution/111.32),y0],
- [x0+(resolution/111.32),y0+(resolution/110.57)],
- [x0,y0+(resolution/110.57)]])}
- c += 1
- y0 += resolution/110.57
- y0 = city_shape.bounds.min().values[1]
- x0 += resolution/111.32
- grid = pd.DataFrame(grid).transpose()
- grid = gpd.GeoDataFrame(grid)
- grid.crs = {'init': 'epsg:4326'}
- grid = grid.to_crs(city_shape.crs)
- grid = gpd.sjoin(grid, city_shape, op='intersects')[grid.columns]
- grid['lat'] = grid.centroid.y
- grid['lon'] = grid.centroid.x
- grid.index.name = 'places'
- return grid[~grid.index.duplicated()]
-
-
-
-
-[docs]
-class QuadratCount(BaseEstimator, TransformerMixin):
-
- def __init__(self, tfreq, grid, filter_place_ratio=0.9):
- self._tfreq = tfreq
- self._grid = grid
- self._filter_place_ratio = filter_place_ratio # pct of timestamps with at least one crime
-
-
-
-
-
-[docs]
- def transform(self, data_points):
- stseries = gpd.sjoin(data_points, self._grid).set_index('t')\
- .groupby([pd.Grouper(freq=self._tfreq), 'index_right'])\
- .size().unstack(fill_value=0).stack()
- stseries.index.names = ['t', 'places']
- c_places = stseries.groupby(['places']).agg(lambda x: x.eq(0).sum())
- n_timestamps = len(stseries.index.get_level_values('t').unique())
- c_places = c_places.loc[c_places < self._filter_place_ratio * n_timestamps].index
- stseries = stseries.loc[pd.IndexSlice[:, c_places]]
- self._grid = self._grid.loc[c_places]
- return stseries
-
-
-
-
-
-[docs]
-class QuadratCount2(BaseEstimator, TransformerMixin):
-
- def __init__(self, tfreq, grid, filter_place_ratio=0.9):
- self._tfreq = tfreq
- self._grid = grid
- self._filter_place_ratio = filter_place_ratio # pct of timestamps with at least one crime
-
-
-
-[docs]
- def transform(self, data_points):
- stseries = gpd.sjoin(data_points, self._grid).set_index('t')\
- .groupby([pd.Grouper(freq=self._tfreq), 'index_right'])\
- .size().unstack(fill_value=0).stack()
- stseries.index.names = ['t', 'places']
- c_places = stseries.groupby(['places']).agg(lambda x: x.eq(0).sum())
- n_timestamps = len(stseries.index.get_level_values('t').unique())
- c_places = c_places.loc[c_places < self._filter_place_ratio * n_timestamps].index
- stseries = stseries.loc[pd.IndexSlice[:, c_places]]
- self._grid = self._grid.loc[c_places]
- return stseries
-
-
-
-
-
-[docs]
-class KGrid:
-
- def __init__(self, k, tfreq):
- self._K = k
- self._tfreq = tfreq
-
-
-[docs]
- def fit(self, data_points):
- self.km = KMeans(self._K).fit(data_points[['lat','lon']])
- crime_data = data_points.copy(deep=True)
- crime_data['K'] = self.km.labels_
- self._grid = gpd.GeoDataFrame(
- geometry=crime_data.groupby('K')\
- .apply(lambda x: MultiPoint(list(x['geometry'])).convex_hull))
- self._grid.crs = {'init': 'epsg:4326'}
- self._grid = self._grid.to_crs(crime_data.crs)
- crimes_per_cell = gpd.sjoin(crime_data, self._grid)\
- .groupby('index_right').size()
- self._grid = self._grid.loc[crimes_per_cell > crimes_per_cell.mean()]
- return self
-
-
-
-[docs]
- def transform(self, data_points):
- stseries = gpd.sjoin(data_points, self._grid).set_index('t')\
- .groupby([pd.Grouper(freq=self._tfreq), 'index_right'])\
- .size().unstack(fill_value=0).stack()
- stseries.index.names = ['t', 'places']
- c_places = stseries.groupby(['places']).agg(lambda x: x.eq(0).sum())
- n_timestamps = len(stseries.index.get_level_values('t').unique())
- c_places = c_places.loc[c_places < 0.9 * n_timestamps].index
- stseries = stseries.loc[pd.IndexSlice[:, c_places]]
- self._grid = self._grid.loc[c_places]
- return stseries
-
-
-
-
-
-[docs]
-class SpatioTemporalMapping(ABC, TransformerMixin, BaseEstimator): # y
- """
- Abstract base class for spatio-temporal crime mapping.
-
- Args:
- tfreq (str): Time frequency ('M' for monthly, 'W' for weekly, 'D' for daily)
- grid (GeoDataFrame): Spatial grid for analysis
- start_time (str or datetime, optional): Analysis start time
- end_time (str or datetime, optional): Analysis end time
- """
-
- def __init__(self, tfreq, grid, start_time=False, end_time=False, debug=False):
- self.debug = debug
- if self.debug:
- print(f"Initializing SpatioTemporalMapping with frequency: {tfreq}", flush=True)
- assert tfreq.upper() in ['M', 'W', 'D'], \
- "Invalid tfreq. Please choose (m)onthly, (w)eekly or (d)aily."
- assert all([x in grid.columns for x in ['geometry', 'lon', 'lat']]), \
- "Input grid must have `geometry`, `lon` and `lat` columns."
- self._tfreq = tfreq.upper()
- self._grid = grid
- self._start_time = pd.to_datetime(start_time) if start_time else False
- self._end_time = pd.to_datetime(end_time) if end_time else False
-
-
-
-
-
-[docs]
- def get_time_data_chunks(self, data_points):
- chunks = data_points.set_index('t').resample(self._tfreq)
- chunks = pd.DataFrame(chunks,
- columns=['t', 'crime_chunks'])
- chunks = chunks.set_index('t').sort_values('t')
- return chunks.apply(lambda x: x[0], axis=1)
-
-
-
-[docs]
- def get_times_no_data(self, chunks):
- if not self._start_time:
- self._start_time = chunks.index.min()
- if not self._end_time:
- self._end_time = chunks.index.max()
- times_between = pd.date_range(self._start_time, self._end_time, freq=self._tfreq)
- no_data = {cell:0 for cell in self._grid.index}
- times_no_data = set(times_between) - set(chunks.index)
- if len(times_no_data) == 0:
- return chunks
- times_no_data = pd.DataFrame(index=times_no_data)
- times_no_data['crime_density'] = [no_data] * len(times_no_data)
- return times_no_data
-
-
-
-
-
-
-[docs]
- def transform(self, data_points):
- chunks = self.get_time_data_chunks(data_points)
- stseries = chunks.apply(self.fit_grid).to_frame('crime_density')
- times_no_data = self.get_times_no_data(chunks)
- if len(times_no_data) != len(chunks):
- stseries = stseries.append(times_no_data)
- time_ix = stseries.index
- stseries = pd.json_normalize(data=stseries['crime_density'])
- stseries.index = time_ix
- stseries = stseries.unstack()
- stseries.index.names = ['places','t']
- stseries = stseries.swaplevel().sort_index()
- return stseries
-
-
-
-
-
-[docs]
-class KDE(SpatioTemporalMapping): # y
- """
- Kernel Density Estimation for crime hotspot detection.
-
- Args:
- tfreq (str): Time frequency ('M' for monthly, 'W' for weekly, 'D' for daily)
- grid (GeoDataFrame): Spatial grid for analysis
- start_time (str or datetime, optional): Analysis start time
- end_time (str or datetime, optional): Analysis end time
- bandwidth (str or float): Bandwidth method ('silverman' or numeric value)
- """
-
- def __init__(self, tfreq, grid, start_time=False, end_time=False, bandwidth='silverman', debug=False):
- super().__init__(tfreq, grid, start_time, end_time)
- self.debug = debug
- self._bandwidth = bandwidth
- self._kernel = None
-
- if self.debug:
- print(f"Initializing KDE with bandwidth: {bandwidth}", flush=True)
-
-
-[docs]
- def fit_grid(self, data_points, as_df=False):
- """
- Fit the kernel density estimation to grid points.
-
- Args:
- data_points (GeoDataFrame): Crime incident points
- as_df (bool): If True, return results as DataFrame
-
- Returns:
- dict or DataFrame: Density estimates for grid points
- """
- if self.debug:
- print(f"Fitting KDE grid with {len(data_points)} points", flush=True)
-
- if len(data_points) < 3:
- crime_density = pd.DataFrame([0]*len(self._grid.index),
- index=self._grid.index,
- columns=['crime_density'])
- else:
- if isinstance(self._bandwidth, str):
- self._kernel = gaussian_kde(np.vstack([data_points.centroid.x,
- data_points.centroid.y]),
- bw_method='silverman')
- self._bandwidth = self._kernel.factor
- else:
- self._kernel = gaussian_kde(np.vstack([data_points.centroid.x,
- data_points.centroid.y]),
- bw_method=self._bandwidth)
- crime_density = pd.DataFrame(self._kernel(self._grid[['lon', 'lat']].values.T),
- index=self._grid.index, columns=['crime_density'])
- if as_df:
- return crime_density
- return crime_density.to_dict()['crime_density']
-
-
-
-"""
-Dataset Preparation Module
-
-This module provides functionality for preparing and managing crime datasets along with their
-corresponding study areas. It handles spatial data processing and visualization of crime incidents.
-"""
-
-__author__ = 'Adelson Araujo'
-
-import pandas as pd
-import geopandas as gpd
-import numpy as np
-import warnings
-from shapely.geometry import Point, LineString
-
-
-
-[docs]
-class Dataset:
- """
- A class to handle crime datasets and their associated study areas.
-
- Args:
- crimes (pandas.DataFrame): DataFrame containing crime data with required columns:
- 'tag', 't' (timestamp), 'lon' (longitude), and 'lat' (latitude)
- study_area (geopandas.GeoDataFrame): GeoDataFrame defining the study area boundaries
- debug (bool, optional): Enable debug printing. Defaults to False.
-
- Attributes:
- crimes (geopandas.GeoDataFrame): Processed crime data with geometry
- study_area (geopandas.GeoDataFrame): Study area boundaries
- """
-
- def __init__(self, crimes, study_area, debug=False):#, poi_data=None):
- self.debug = debug
-
- if self.debug:
- print("Initializing Dataset class", flush=True)
-
- assert isinstance(study_area, gpd.GeoDataFrame), \
- "study_area must be a geopandas GeoDataFrame."
- self._study_area = study_area
-
- assert isinstance(crimes, pd.DataFrame) \
- and all([x in crimes.columns for x in ['tag', 't', 'lon', 'lat']]),\
- "Input crime data must be a pandas Data Frame and " \
- + "have at least `tag`, `t`, `lon` and `lat` as columns."
-
- if self.debug:
- print(f"Processing {len(crimes)} crime incidents", flush=True)
-
- self._crimes = crimes
- self._crimes['geometry'] = self._crimes.apply(lambda x: Point([x['lon'], x['lat']]),
- axis=1)
- self._crimes = gpd.GeoDataFrame(self._crimes, crs={'init': 'epsg:4326'})
- self._crimes['t'] = self._crimes['t'].apply(pd.to_datetime)
-
- if self.debug:
- print("Dataset initialization complete", flush=True)
-
- def __repr__(self):
- """
- String representation of the Dataset object.
-
- Returns:
- str: A formatted string showing dataset statistics
- """
- return 'predspot.Dataset<\n'\
- + f' crimes = GeoDataFrame({self._crimes.shape[0]}),\n' \
- + f' >> {self.crimes["tag"].value_counts().to_dict()}\n' \
- + f' study_area = GeoDataFrame({self._study_area.shape[0]}),\n' \
- + '>'
-
- @property
- def crimes(self):
- """
- Get the crime incidents data.
-
- Returns:
- geopandas.GeoDataFrame: The processed crime incidents data
- """
- return self._crimes
-
- @property
- def study_area(self):
- """
- Get the study area boundaries.
-
- Returns:
- geopandas.GeoDataFrame: The study area boundaries
- """
- return self._study_area
-
- @property
- def shape(self):
- """
- Get the shapes of the dataset components.
-
- Returns:
- dict: Dictionary containing the shapes of crimes and study_area DataFrames
- """
- return {'crimes': self._crimes.shape,
- 'study_area': self._study_area.shape}
-
-
-[docs]
- def plot(self, ax=None, crime_samples=1000, **kwargs):
- """
- Plot the study area and crime incidents.
-
- Args:
- ax (matplotlib.axes.Axes, optional): Matplotlib axes for plotting
- crime_samples (int, optional): Number of crime samples to plot. Defaults to 1000
- **kwargs: Additional keyword arguments for plotting
- study_area: kwargs for study area plot
- crimes: kwargs for crime incidents plot
-
- Returns:
- matplotlib.axes.Axes: The plot axes
- """
- if self.debug:
- print(f"Plotting dataset with {crime_samples} sample points", flush=True)
-
- if ax is None:
- ax = self.study_area.plot(color='white', edgecolor='black',
- **kwargs.pop('study_area',{}))
- else:
- self.study_area.plot(color='white', edgecolor='black', ax=ax,
- **kwargs.pop('study_area',{}))
- if crime_samples > len(self.crimes):
- crime_samples = len(self.crimes)
- self.crimes.sample(crime_samples).plot(ax=ax, marker='x',
- **kwargs.pop('crimes',{}))
- return ax
-
-
-
-[docs]
- def train_test_split(self, test_size=0.25):
- """
- Split the dataset into training and testing sets.
-
- Args:
- test_size (float): Proportion of the dataset to include in the test split.
- Must be between 0 and 1. Defaults to 0.25.
-
- Returns:
- tuple: (train_dataset, test_dataset) - Two Dataset objects containing the splits
-
- Raises:
- AssertionError: If test_size is not between 0 and 1
- """
- if self.debug:
- print(f"Splitting dataset with test_size={test_size}", flush=True)
-
- assert 0 < test_size < 1, \
- 'test_size must be between 0 and 1.'
- test_dataset = Dataset(self.crimes.sample(frac=test_size), self.study_area)
- train_ix = set(self.crimes.index) - set(test_dataset.crimes.index)
- train_dataset = Dataset(self.crimes.loc[train_ix], self.study_area)
-
- if self.debug:
- print(f"Split complete - Train size: {len(train_dataset.crimes)}, "
- f"Test size: {len(test_dataset.crimes)}", flush=True)
-
- return train_dataset, test_dataset
-
-
-
-"""
-Feature Engineering Module
-
-This module provides classes for time series feature engineering and transformation,
-including autoregressive features, differencing, seasonality, and trend decomposition.
-"""
-
-__author__ = 'Adelson Araujo'
-
-from abc import abstractmethod
-from numpy import vstack
-import pandas as pd
-from sklearn.base import BaseEstimator, TransformerMixin
-from statsmodels.tsa.seasonal import STL
-
-
-
-[docs]
-class TimeSeriesFeatures(BaseEstimator, TransformerMixin):
- """
- Base class for time series feature engineering.
-
- Args:
- lags (int): Number of time lags to use for feature creation
- tfreq (str): Time frequency ('D' for daily, 'W' for weekly, 'M' for monthly)
- debug (bool, optional): Enable debug printing. Defaults to False
-
- Raises:
- AssertionError: If lags is not a positive integer or tfreq is invalid
- """
-
- def __init__(self, lags, tfreq, debug=False):
- self.debug = debug
- if self.debug:
- print(f"Initializing TimeSeriesFeatures with lags={lags}, freq={tfreq}", flush=True)
-
- assert isinstance(lags, int) and lags > 1, \
- '`lags` must be a positive integer.'
- self._lags = lags
- assert tfreq in ['D', 'W', 'M'], \
- '`tfreq` not allowed, choose between `D`, `W`, `M`.'
- self._tfreq = tfreq
-
- @property
- def lags(self):
- """int: Number of time lags"""
- return self._lags
-
- @property
- def label(self):
- """str: Feature label identifier"""
- return 'feature' # override this label if implementing a new feature
-
-
-[docs]
- @abstractmethod
- def apply_ts_decomposition(self, ts):
- """
- Apply time series decomposition.
-
- Args:
- ts (pandas.Series): Input time series
-
- Returns:
- pandas.Series: Transformed time series
- """
- pass
-
-
-
-[docs]
- def make_lag_df(self, ts):
- """
- Create lagged features dataframe.
-
- Args:
- ts (pandas.Series): Input time series
-
- Returns:
- tuple: (lag_df, aligned_ts) - Lagged features and aligned original series
-
- Raises:
- AssertionError: If series length is less than number of lags
- """
- if self.debug:
- print(f"Creating lag features for series of length {len(ts)}", flush=True)
-
- assert len(ts) > self.lags, "`lags` are higher than temporal units."
- lag_df = pd.concat([ts.shift(lag) for lag in range(1, self.lags+1)], axis=1)
- lag_df = lag_df.iloc[self.lags:]
- lag_df.columns = ['{}_{}'.format(self.label, i) for i in range(1, self.lags+1)]
- return lag_df, ts.loc[lag_df.index]
-
-
-
-[docs]
- def transform(self, stseries):
- """
- Transform the input series into lagged features.
-
- Args:
- stseries (pandas.Series): Input time series with multi-index (time, places)
-
- Returns:
- pandas.DataFrame: Transformed features
- """
- if self.debug:
- print(f"Transforming series with {len(stseries)} observations", flush=True)
-
- X = pd.DataFrame()
- if self._tfreq=='M':
- next_time = pd.tseries.offsets.MonthEnd(1)
- elif self._tfreq=='W':
- next_time = pd.tseries.offsets.Week(1)
- elif self._tfreq=='D':
- next_time = pd.tseries.offsets.Day(1)
-
- places = stseries.index.get_level_values('places').unique()
- for place in places:
- if self.debug:
- print(f"Processing features for place: {place}", flush=True)
-
- ts = stseries.loc[pd.IndexSlice[:, place]]
- ts = self.apply_ts_decomposition(ts)
- ts.loc[ts.index[-1] + next_time] = None
- f, _ = self.make_lag_df(ts)
- f['places'] = place
- f = f.set_index('places', append=True)
- X = X.append(f)
- X = X.sort_index()
- return X
-
-
-
-
-
-[docs]
-class AR(TimeSeriesFeatures):
- """
- Autoregressive features implementation.
- """
-
- @property
- def label(self):
- """str: Feature label for autoregressive features"""
- return 'ar'
-
-
-[docs]
- def apply_ts_decomposition(self, ts):
- """
- Apply autoregressive transformation (identity).
-
- Args:
- ts (pandas.Series): Input time series
-
- Returns:
- pandas.Series: Original time series
- """
- return ts
-
-
-
-
-
-[docs]
-class Diff(TimeSeriesFeatures):
- """
- Difference features implementation.
- """
-
- @property
- def label(self):
- """str: Feature label for difference features"""
- return 'diff'
-
-
-[docs]
- def apply_ts_decomposition(self, ts):
- """
- Apply difference transformation.
-
- Args:
- ts (pandas.Series): Input time series
-
- Returns:
- pandas.Series: Differenced time series
- """
- return ts.diff()[1:]
-
-
-
-
-
-[docs]
-class Seasonality(TimeSeriesFeatures):
- """
- Seasonal decomposition features implementation.
- """
-
- @property
- def label(self):
- """str: Feature label for seasonal features"""
- return 'seasonal'
-
-
-[docs]
- def apply_ts_decomposition(self, ts):
- """
- Extract seasonal component from time series.
-
- Args:
- ts (pandas.Series): Input time series
-
- Returns:
- pandas.Series: Seasonal component
- """
- if self.debug:
- print(f"Extracting seasonality with period={self._lags}", flush=True)
- return STL(ts, period=self._lags).seasonal
-
-
-
-
-
-[docs]
-class Trend(TimeSeriesFeatures):
- """
- Trend decomposition features implementation.
- """
-
- @property
- def label(self):
- """str: Feature label for trend features"""
- return 'trend'
-
-
-[docs]
- def apply_ts_decomposition(self, ts):
- """
- Extract trend component from time series.
-
- Args:
- ts (pandas.Series): Input time series
-
- Returns:
- pandas.Series: Trend component
- """
- if self.debug:
- print(f"Extracting trend with period={self._lags}", flush=True)
- return STL(ts, period=self._lags).trend
-
-
-
-
-
-[docs]
-class FeatureScaling(TransformerMixin, BaseEstimator):
- """
- Feature scaling transformer.
-
- Args:
- estimator: Scikit-learn compatible scaling estimator
- debug (bool, optional): Enable debug printing. Defaults to False
- """
-
- def __init__(self, estimator, debug=False):
- self.debug = debug
- self._estimator = estimator
-
- if self.debug:
- print("Initializing FeatureScaling", flush=True)
-
-
-[docs]
- def transform(self, x):
- """
- Transform features using the scaling estimator.
-
- Args:
- x (pandas.DataFrame): Input features
-
- Returns:
- pandas.DataFrame: Scaled features
- """
- if self.debug:
- print(f"Scaling features of shape {x.shape}", flush=True)
-
- return pd.DataFrame(
- self._estimator.transform(x),
- index=x.index,
- columns=x.columns
- )
-
-
-
-"""
-Machine Learning Modelling Module
-
-This module provides classes for machine learning model pipelines, feature selection,
-and prediction functionality for crime density forecasting.
-"""
-
-__author__ = 'Adelson Araujo'
-
-import pandas as pd
-import numpy as np
-from sklearn.base import BaseEstimator, TransformerMixin, RegressorMixin
-from sklearn.pipeline import Pipeline
-from sklearn.metrics import r2_score, mean_squared_error
-from sklearn.model_selection import TimeSeriesSplit
-
-idx = pd.IndexSlice
-
-
-
-[docs]
-class FeatureSelection(TransformerMixin, BaseEstimator):
- """
- Feature selection transformer.
-
- Args:
- estimator: Scikit-learn compatible feature selector
- debug (bool, optional): Enable debug printing. Defaults to False
- """
-
- def __init__(self, estimator, debug=False):
- self._estimator = estimator
- self.debug = debug
-
- if self.debug:
- print("Initializing FeatureSelection", flush=True)
-
-
-[docs]
- def fit(self, x, y=None):
- """
- Fit the feature selector.
-
- Args:
- x (pandas.DataFrame): Input features
- y (pandas.Series, optional): Target variable
-
- Returns:
- self: The fitted instance
- """
- if self.debug:
- print(f"Fitting feature selector with {x.shape[1]} features", flush=True)
-
- self._estimator.fit(x, y)
- return self
-
-
-
-[docs]
- def transform(self, x):
- """
- Transform features using the feature selector.
-
- Args:
- x (pandas.DataFrame): Input features
-
- Returns:
- pandas.DataFrame: Selected features
- """
- if self.debug:
- print(f"Transforming features, selecting {sum(self._estimator.support_)} features", flush=True)
-
- return pd.DataFrame(
- self._estimator.transform(x),
- index=x.index,
- columns=x.columns[self._estimator.support_]
- )
-
-
-
-
-
-[docs]
-class Model(RegressorMixin, BaseEstimator):
- """
- Model wrapper for crime density prediction.
-
- Args:
- estimator: Scikit-learn compatible regression estimator
- debug (bool, optional): Enable debug printing. Defaults to False
- """
-
- def __init__(self, estimator, debug=False):
- self._estimator = estimator
- self.debug = debug
-
- if self.debug:
- print("Initializing Model wrapper", flush=True)
-
-
-[docs]
- def fit(self, x, y=None):
- """
- Fit the regression model.
-
- Args:
- x (pandas.DataFrame): Input features
- y (pandas.Series, optional): Target variable
-
- Returns:
- self: The fitted instance
- """
- if self.debug:
- print(f"Fitting model with {x.shape[1]} features", flush=True)
-
- self._estimator.fit(x, y)
- return self
-
-
-
-[docs]
- def predict(self, x):
- """
- Make predictions using the fitted model.
-
- Args:
- x (pandas.DataFrame): Input features
-
- Returns:
- pandas.DataFrame: Predictions with 'crime_density' column
- """
- if self.debug:
- print(f"Making predictions for {len(x)} instances", flush=True)
-
- return pd.DataFrame(
- self._estimator.predict(x),
- index=x.index,
- columns=['crime_density']
- )
-
-
-
-
-
-[docs]
-class PredictionPipeline(RegressorMixin, BaseEstimator):
- """
- Complete pipeline for crime density prediction.
-
- Args:
- mapping: Spatial mapping transformer
- fextraction: Feature extraction transformer
- estimator: Scikit-learn compatible pipeline or estimator
- debug (bool, optional): Enable debug printing. Defaults to False
- """
-
- def __init__(self, mapping, fextraction, estimator, debug=False):
- self._mapping = mapping
- self._fextraction = fextraction
- self._estimator = estimator
- self._stseries = None
- self._dataset = None
- self.debug = debug
-
- if self.debug:
- print("Initializing PredictionPipeline", flush=True)
-
- if mapping._tfreq == 'M':
- self._offset = pd.tseries.offsets.MonthEnd(1)
- elif mapping._tfreq == 'W':
- self._offset = pd.tseries.offsets.Week(1)
- elif mapping._tfreq == 'D':
- self._offset = pd.tseries.offsets.Day(1)
-
- @property
- def grid(self):
- """GeoDataFrame: Spatial grid used for mapping"""
- return self._mapping._grid
-
- @property
- def dataset(self):
- """Dataset: Current dataset being used"""
- return self._dataset
-
- @property
- def stseries(self):
- """pandas.Series: Spatio-temporal series"""
- return self._stseries
-
- @property
- def feature_importances(self):
- """
- Get feature importance scores.
-
- Returns:
- pandas.DataFrame: Feature importance scores
-
- Raises:
- Exception: If model hasn't been fitted or doesn't support feature importances
- """
- assert self._X is not None, 'this instance was not fitted yet.'
- try:
- return pd.DataFrame(
- self._estimator.steps[-1][1]._estimator.feature_importances_,
- index=self._X.columns[self._estimator.steps[-2][1]._estimator.support_],
- columns=['importance']
- )
- except:
- raise Exception('estimator used has not feature importances implemented yet.')
-
-
-[docs]
- def evaluate(self, scoring, cv=5):
- """
- Evaluate model performance using time series cross-validation.
-
- Args:
- scoring (str): Scoring metric ('r2' or 'mse')
- cv (int): Number of cross-validation folds
-
- Returns:
- list: Scores for each fold
-
- Raises:
- Exception: If scoring metric is invalid
- """
- if self.debug:
- print(f"Evaluating model with {cv}-fold time series CV", flush=True)
-
- assert self._X is not None, 'this instance was not fitted yet.'
- if scoring == 'r2':
- scoring = r2_score
- elif scoring == 'mse':
- scoring = mean_squared_error
- else:
- raise Exception('invalid scoring. Try "r2" or "mse".')
-
- timestamps = self._X.index.get_level_values('t').unique()\
- .intersection(self._stseries.index.get_level_values('t').unique())
- assert isinstance(cv, int) and cv < len(timestamps), \
- 'cv must be an integer and not higher than the number of timestamps available.'
-
- scores = []
- for train_t, test_t in TimeSeriesSplit(cv).split(timestamps):
- if self.debug:
- print(f"CV fold - train size: {len(train_t)}, test size: {len(test_t)}", flush=True)
-
- X_train = self._X.loc[idx[timestamps[train_t], :], :].sample(frac=1)
- X_test = self._X.loc[idx[timestamps[test_t], :], :]
- y_train = self._stseries.loc[X_train.index]
- y_test = self._stseries.loc[timestamps[test_t]]
- self._estimator.fit(X_train, y_train)
- y_pred = self._estimator.predict(X_test)
- scores.append(scoring(y_test, y_pred))
-
- self.fit(self._dataset) # back to normal
- return scores
-
-
-
-[docs]
- def fit(self, dataset, y=None):
- """
- Fit the complete prediction pipeline.
-
- Args:
- dataset: Input dataset containing crimes and study area
- y: Ignored, present for scikit-learn compatibility
-
- Returns:
- self: The fitted instance
-
- Raises:
- Exception: If fitting fails
- """
- if self.debug:
- print("Fitting prediction pipeline", flush=True)
-
- self._dataset = dataset
- self._stseries = self._mapping.fit_transform(dataset.crimes)
- self._X = self._fextraction.fit_transform(self._stseries)
- t0 = self._X.index.get_level_values('t').unique().min()
- tf = self._stseries.index.get_level_values('t').unique().max()
- X = self._X.loc[t0:tf].sample(frac=1) # shuffle for training
- y = self._stseries.loc[X.index] # shuffle for training
-
- try:
- self._estimator.fit(X, y)
- except Exception as e:
- raise Exception(f'ERROR: {t0}, {tf} \nX: {self._X.loc[t0:tf]}') from e
-
- self._t_plus_one = self._X.index.get_level_values('t').unique()[-1]
- return self
-
-
-
-[docs]
- def predict(self):
- """
- Make predictions for the next time step.
-
- Returns:
- pandas.DataFrame: Predictions for next time step
- """
- if self.debug:
- print(f"Predicting for time step: {self._t_plus_one}", flush=True)
-
- X = self._X.loc[[self._t_plus_one],:]
- y_pred = self._estimator.predict(X)
- y_pred = pd.DataFrame(y_pred, index=X.index)
- self._stseries = self._stseries.append(y_pred['crime_density'])
- self._X = self._fextraction.transform(self._stseries)
- self._t_plus_one += self._offset
- return y_pred
-
-
-
-"""
-Pipeline Module
-
-This module provides the main pipeline functionality for crime prediction,
-including data loading, preprocessing, and model execution.
-
-Example:
- >>> from predspot.pipeline import generate_testdata, run_prediction_pipeline
- >>> crime_data, study_area = generate_testdata(10000, '2020-01-01', '2020-12-31')
- >>> results = run_prediction_pipeline(crime_data, study_area)
-"""
-
-__author__ = 'Adelson Araujo'
-
-import numpy as np
-import pandas as pd
-import geopandas as gpd
-from sklearn.pipeline import Pipeline
-from sklearn.preprocessing import QuantileTransformer
-from sklearn.feature_selection import RFE
-from sklearn.ensemble import RandomForestRegressor
-
-from predspot import dataset_preparation
-from predspot import crime_mapping
-from predspot import feature_engineering
-from predspot import ml_modelling
-from predspot.utilities import PandasFeatureUnion
-
-
-
-[docs]
-def generate_testdata(n_points, start_time, end_time, debug=False):
- """
- Generate synthetic crime data for testing.
-
- Args:
- n_points (int): Number of crime incidents to generate
- start_time (str): Start date in 'YYYY-MM-DD' format
- end_time (str): End date in 'YYYY-MM-DD' format
- debug (bool, optional): Enable debug printing. Defaults to False
-
- Returns:
- tuple: (crimes_df, study_area_gdf) - Generated crime data and study area
-
- Example:
- >>> crimes, area = generate_testdata(1000, '2020-01-01', '2020-12-31')
- """
- if debug:
- print(f"Generating {n_points} test data points from {start_time} to {end_time}", flush=True)
-
- study_area = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
- study_area = study_area.loc[study_area['name']=='Brazil']
-
- crimes = pd.DataFrame()
- crime_types = pd.Series(['burglary', 'assault', 'drugs', 'homicide'])
- bounds = study_area.geometry.bounds.values[0]
-
- if debug:
- print("Generating random dates and locations", flush=True)
-
- def random_dates(start, end, n=10):
- """Generate random dates within a range."""
- start, end = pd.to_datetime(start), pd.to_datetime(end)
- start_u = start.value//10**9
- end_u = end.value//10**9
- return pd.to_datetime(np.random.randint(start_u, end_u, n), unit='s')
-
- crimes['tag'] = crime_types.sample(n_points, replace=True,
- weights=[1000, 100, 10, 1])
- crimes['t'] = random_dates(start_time, end_time, n_points)
- crimes['lat'] = np.random.uniform(bounds[1], bounds[3], n_points)
- crimes['lon'] = np.random.uniform(bounds[0], bounds[2], n_points)
- crimes.reset_index(drop=True, inplace=True)
-
- if debug:
- print("Test data generation complete", flush=True)
-
- return crimes, study_area
-
-
-
-
-[docs]
-def run_prediction_pipeline(crime_data, study_area, crime_tags=None, time_range=None,
- tfreq='M', grid_resolution=250, debug=False):
- """
- Run the complete crime prediction pipeline.
-
- Args:
- crime_data (pandas.DataFrame): Crime incident data
- study_area (geopandas.GeoDataFrame): Study area boundaries
- crime_tags (list, optional): List of crime types to include
- time_range (list, optional): Time range as ['HH:MM', 'HH:MM']
- tfreq (str, optional): Time frequency ('M', 'W', 'D'). Defaults to 'M'
- grid_resolution (float, optional): Spatial grid resolution in km. Defaults to 250
- debug (bool, optional): Enable debug printing. Defaults to False
-
- Returns:
- tuple: (predictions, pipeline) - Predicted crime densities and fitted pipeline
-
- Raises:
- ValueError: If input data is invalid or missing required columns
- """
- if debug:
- print("Initializing prediction pipeline", flush=True)
-
- # Validate input data
- required_columns = ['tag', 't', 'lat', 'lon']
- if not all(col in crime_data.columns for col in required_columns):
- raise ValueError(f"Crime data must contain columns: {required_columns}")
-
- # Filter by crime tags if specified
- if crime_tags:
- if debug:
- print(f"Filtering for crime types: {crime_tags}", flush=True)
- crime_data = crime_data.loc[crime_data['tag'].isin(crime_tags)]
-
- # Filter by time range if specified
- if time_range:
- if debug:
- print(f"Filtering for time range: {time_range}", flush=True)
- time_ix = pd.DatetimeIndex(crime_data['t'])
- crime_data = crime_data.iloc[time_ix.indexer_between_time(time_range[0], time_range[1])]
-
- if debug:
- print("Creating dataset", flush=True)
- dataset = dataset_preparation.Dataset(crimes=crime_data, study_area=study_area)
-
- if debug:
- print("Building prediction pipeline", flush=True)
- pred_pipeline = ml_modelling.PredictionPipeline(
- mapping=crime_mapping.KDE(
- tfreq=tfreq,
- bandwidth='auto',
- grid=crime_mapping.create_gridpoints(study_area, grid_resolution)
- ),
- fextraction=PandasFeatureUnion([
- ('seasonal', feature_engineering.Seasonality(lags=2)),
- ('trend', feature_engineering.Trend(lags=2)),
- ('diff', feature_engineering.Diff(lags=2))
- ]),
- estimator=Pipeline([
- ('f_scaling', feature_engineering.FeatureScaling(
- QuantileTransformer(10, output_distribution='uniform'))),
- ('f_selection', ml_modelling.FeatureSelection(
- RFE(RandomForestRegressor()))),
- ('model', ml_modelling.Model(RandomForestRegressor(n_estimators=50)))
- ])
- )
-
- if debug:
- print("Fitting pipeline", flush=True)
- pred_pipeline.fit(dataset)
-
- if debug:
- print("Making predictions", flush=True)
- predictions = pred_pipeline.predict()
-
- if debug:
- print("Pipeline execution complete", flush=True)
-
- return predictions, pred_pipeline
-
-
-
-
-[docs]
-def evaluate_pipeline(pipeline, scoring='r2', cv=5, debug=False):
- """
- Evaluate the prediction pipeline using cross-validation.
-
- Args:
- pipeline (PredictionPipeline): Fitted prediction pipeline
- scoring (str, optional): Scoring metric ('r2' or 'mse'). Defaults to 'r2'
- cv (int, optional): Number of cross-validation folds. Defaults to 5
- debug (bool, optional): Enable debug printing. Defaults to False
-
- Returns:
- list: Cross-validation scores
-
- Example:
- >>> scores = evaluate_pipeline(fitted_pipeline, scoring='r2', cv=5)
- """
- if debug:
- print(f"Evaluating pipeline with {cv}-fold CV using {scoring} metric", flush=True)
-
- scores = pipeline.evaluate(scoring=scoring, cv=cv)
-
- if debug:
- print(f"Evaluation complete. Mean score: {np.mean(scores):.4f}", flush=True)
-
- return scores
-
-
-"""
-Utilities Module
-
-This module provides utility functions and classes for data processing and visualization,
-including contour generation, feature union operations, and pandas-specific transformations.
-"""
-
-__author__ = 'Adelson Araujo'
-
-import matplotlib
-matplotlib.use('Agg')
-import matplotlib.pyplot as plt
-import pandas as pd
-from geopandas import GeoDataFrame
-from numpy import zeros, linspace, ceil, meshgrid
-from sklearn.pipeline import FeatureUnion, Pipeline, _fit_transform_one, _transform_one
-from joblib import Parallel, delayed
-from scipy import sparse
-import geojsoncontour
-
-
-
-[docs]
-def contour_geojson(y, bbox, resolution, cmin, cmax, debug=False):
- """
- Generate GeoJSON contours from spatial data.
-
- Args:
- y (pandas.Series): Values to contour
- bbox (GeoDataFrame): Bounding box for the contour
- resolution (float): Spatial resolution in kilometers
- cmin (float): Minimum contour value
- cmax (float): Maximum contour value
- debug (bool, optional): Enable debug printing. Defaults to False
-
- Returns:
- dict: GeoJSON representation of the contours
-
- Raises:
- AssertionError: If bbox is not a GeoDataFrame
- """
- if debug:
- print(f"Generating contours with resolution: {resolution}km", flush=True)
-
- assert isinstance(bbox, GeoDataFrame)
- bounds = bbox.bounds
- b_s, b_w = bounds.min().values[1], bounds.min().values[0]
- b_n, b_e = bounds.max().values[3], bounds.max().values[2]
-
- # Calculate grid dimensions
- nlon = int(ceil((b_e-b_w) / (resolution/111.32)))
- nlat = int(ceil((b_n-b_s) / (resolution/110.57)))
-
- if debug:
- print(f"Grid dimensions: {nlon}x{nlat}", flush=True)
-
- # Create meshgrid and initialize values
- lonv, latv = meshgrid(linspace(b_w, b_e, nlon), linspace(b_s, b_n, nlat))
- Z = zeros(lonv.shape[0]*lonv.shape[1]) - 999
- Z[y.index] = y.values
- Z = Z.reshape(lonv.shape)
-
- # Generate contours
- fig, axes = plt.subplots()
- contourf = axes.contourf(lonv, latv, Z,
- levels=linspace(cmin, cmax, 25),
- cmap='Spectral_r')
-
- if debug:
- print("Converting contours to GeoJSON", flush=True)
-
- geojson = geojsoncontour.contourf_to_geojson(contourf=contourf, fill_opacity=0.5)
- plt.close(fig)
- return geojson
-
-
-
-
-[docs]
-class PandasFeatureUnion(FeatureUnion):
- """
- A FeatureUnion transformer that preserves pandas DataFrames.
-
- This class extends sklearn's FeatureUnion to work with pandas DataFrames,
- maintaining index alignment and column names.
-
- Attributes:
- n_jobs (int): Number of parallel jobs
- transformer_list (list): List of transformer tuples
- transformer_weights (dict): Weights for transformers
- debug (bool): Enable debug printing
- """
-
- def __init__(self, transformer_list, n_jobs=None, transformer_weights=None, debug=False):
- super().__init__(transformer_list, n_jobs, transformer_weights)
- self.debug = debug
-
- if self.debug:
- print("Initializing PandasFeatureUnion", flush=True)
-
-
-[docs]
- def fit_transform(self, X, y=None, **fit_params):
- """
- Fit all transformers and transform the data.
-
- Args:
- X (pandas.DataFrame): Input features
- y (array-like, optional): Target values
- **fit_params: Additional fitting parameters
-
- Returns:
- pandas.DataFrame: Transformed features
-
- Raises:
- ValueError: If no transformers are provided
- """
- if self.debug:
- print(f"Fitting and transforming {len(X)} samples", flush=True)
-
- self._validate_transformers()
- result = Parallel(n_jobs=self.n_jobs)(
- delayed(_fit_transform_one)(
- transformer=trans,
- X=X,
- y=y,
- weight=weight,
- **fit_params)
- for name, trans, weight in self._iter())
-
- if not result:
- # All transformers are None
- return zeros((X.shape[0], 0))
-
- Xs, transformers = zip(*result)
- self._update_transformer_list(transformers)
-
- if self.debug:
- print("Merging transformed features", flush=True)
-
- if any(sparse.issparse(f) for f in Xs):
- Xs = sparse.hstack(Xs).tocsr()
- else:
- Xs = self.merge_dataframes_by_column(Xs)
- return Xs
-
-
-
-[docs]
- def merge_dataframes_by_column(self, Xs):
- """
- Merge transformed features into a single DataFrame.
-
- Args:
- Xs (list): List of transformed DataFrames
-
- Returns:
- pandas.DataFrame: Merged DataFrame
- """
- if self.debug:
- print(f"Merging {len(Xs)} DataFrames", flush=True)
-
- return pd.concat(Xs, axis="columns", copy=False).dropna()
-
-
-
-[docs]
- def transform(self, X):
- """
- Transform X separately by each transformer.
-
- Args:
- X (pandas.DataFrame): Input features
-
- Returns:
- pandas.DataFrame: Transformed features
- """
- if self.debug:
- print(f"Transforming features with {len(self.transformer_list)} transformers", flush=True)
-
- Xs = Parallel(n_jobs=self.n_jobs)(
- delayed(_transform_one)(
- transformer=trans,
- X=X,
- y=None,
- weight=weight)
- for name, trans, weight in self._iter())
-
- if not Xs:
- # All transformers are None
- return zeros((X.shape[0], 0))
-
- if any(sparse.issparse(f) for f in Xs):
- Xs = sparse.hstack(Xs).tocsr()
- else:
- Xs = self.merge_dataframes_by_column(Xs)
- return Xs
-
-
-' + - '' + - _("Hide Search Matches") + - "
" - ) - ); - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords: () => { - document - .querySelectorAll("#searchbox .highlight-link") - .forEach((el) => el.remove()); - document - .querySelectorAll("span.highlighted") - .forEach((el) => el.classList.remove("highlighted")); - localStorage.removeItem("sphinx_highlight_terms") - }, - - initEscapeListener: () => { - // only install a listener if it is really needed - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; - - document.addEventListener("keydown", (event) => { - // bail for input elements - if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; - // bail with special keys - if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; - if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { - SphinxHighlight.hideSearchWords(); - event.preventDefault(); - } - }); - }, -}; - -_ready(() => { - /* Do not call highlightSearchWords() when we are on the search page. - * It will highlight words from the *previous* search query. - */ - if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); - SphinxHighlight.initEscapeListener(); -}); diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html deleted file mode 100644 index 4ac4e46..0000000 --- a/docs/build/html/genindex.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - -Predspot is an early project for a Python library for spatio-temporal crime prediction and hotspot detection. It combines machine learning techniques with spatial analysis to help predict and visualize crime patterns across time and space. To properly use this library, you preferably need to have a crime dataset and a study area.
-The results of this library are not guaranteed to be good, but it is a good starting point for spatio-temporal crime prediction. This project is barely maintained, so please if you want to collaborate, open an issue or a PR.
-Warning
-This project was developed as part of a master’s thesis and is currently in an archived state. While the core functionality exists, you may encounter compatibility issues with newer Python package versions. The code can work with some effort, but please note:
-This is not production-ready software
Some dependencies are outdated and may require specific versions
You might need to modify some code to work with newer package versions
The project was created for research purposes
However, we believe the methodologies and approaches used here are still valuable! If you’re interested in crime hotspot prediction, feel free to:
-Use this as a reference implementation
Adapt the code to modern dependencies
Build upon these concepts for your own projects
Contribute to modernizing the codebase
We welcome anyone interested in reviving or learning from this project!
-Spatial and temporal crime mapping
Feature engineering for time series data
Machine learning-based prediction pipeline
Crime hotspot detection using Kernel Density Estimation
Visualization tools for crime patterns
Basic usage example:
-from predspot import Dataset, PredictionPipeline
-from predspot.crime_mapping import KDE, create_gridpoints
-from predspot.feature_engineering import Seasonality, Trend, Diff
-
-# Load and prepare data
-dataset = Dataset(crimes_df, study_area_gdf)
-
-# Create prediction pipeline
-pipeline = PredictionPipeline(
- mapping=KDE(tfreq='M', grid=create_gridpoints(study_area, resolution=250)),
- fextraction=PandasFeatureUnion([
- ('seasonal', Seasonality(lags=12)),
- ('trend', Trend(lags=12)),
- ('diff', Diff(lags=12))
- ]),
- estimator=your_favorite_sklearn_model
-)
-
-# Fit and predict
-pipeline.fit(dataset)
-predictions = pipeline.predict()
-The library consists of four main modules:
-Dataset Preparation: Module for preparing and managing crime datasets and study areas.
Crime Mapping: Module for spatial and temporal crime mapping, including KDE-based hotspot detection.
Feature Engineering: Module for time series feature engineering, including seasonality, trend, and difference features.
ML Modelling: Module that implements the prediction pipeline and model evaluation.
And two utilities:
- -Create conda env and install requirements:
-conda create -n predspot python=3.8
-conda activate predspot
-conda install -y rtree geopandas # if doesnt work, do: `conda clean --all`
-pip install pandas statsmodels==0.10.2 geojsoncontour stldecompose scikit-learn matplotlib descartes
-pip install .
-Required dependencies:
-pandas
geopandas
numpy
scikit-learn
scipy
stldecompose
matplotlib
The crime data should be a pandas DataFrame with the following required columns:
-tag: Crime type
t: Timestamp
lon: Longitude
lat: Latitude
The study area should be a GeoDataFrame defining the geographical boundaries of interest.
-For more information on the methods used in Predspot, please search more about these methods:
-Kernel Density Estimation for crime hotspot detection
Time series decomposition for feature engineering
Spatio-temporal crime prediction techniques
BSD 3-Clause.
-Contributions are welcome! Please feel free to submit a Pull Request.
-Guidelines for contributing:
-Fork the repository
Create your feature branch
Commit your changes
Push to the branch
Create a new Pull Request
If you use Predspot in your research, please cite us:
-Araujo, A., & Cacho, N. (2019). Predspot: Predicting crime hotspots with machine learning.
-Master's thesis, UFRN (Universidade Federal do Rio Grande do Norte), Natal, Brazil.
-
-Araújo, A., Cacho, N., Bezerra, L., Vieira, C., & Borges, J. (2018, June).
-Towards a crime hotspot detection framework for patrol planning.
-In 2018 IEEE 20th International Conference on High Performance Computing and Communications;
-IEEE 16th International Conference on Smart City;
-IEEE 4th International Conference on Data Science and Systems (HPCC/SmartCity/DSS) (pp. 1256-1263). IEEE.
-Documentation:
- -Crime Mapping Module
-This module provides functionality for spatial and temporal crime mapping analysis. -It includes utilities for creating grid points, hexagonal grids, and implementing -kernel density estimation for crime hotspot detection.
-Bases: SpatioTemporalMapping
Kernel Density Estimation for crime hotspot detection.
-tfreq (str) – Time frequency (‘M’ for monthly, ‘W’ for weekly, ‘D’ for daily)
grid (GeoDataFrame) – Spatial grid for analysis
start_time (str or datetime, optional) – Analysis start time
end_time (str or datetime, optional) – Analysis end time
bandwidth (str or float) – Bandwidth method (‘silverman’ or numeric value)
Fit the kernel density estimation to grid points.
-data_points (GeoDataFrame) – Crime incident points
as_df (bool) – If True, return results as DataFrame
Density estimates for grid points
-dict or DataFrame
-Request metadata passed to the fit method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to fit if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to fit.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
x (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for x parameter in fit.
self – The updated object.
-object
-Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
data_points (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for data_points parameter in transform.
self – The updated object.
-object
-Bases: object
Bases: BaseEstimator, TransformerMixin
Request metadata passed to the fit method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to fit if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to fit.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
x (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for x parameter in fit.
self – The updated object.
-object
-Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
data_points (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for data_points parameter in transform.
self – The updated object.
-object
-Bases: BaseEstimator, TransformerMixin
Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
data_points (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for data_points parameter in transform.
self – The updated object.
-object
-Bases: ABC, TransformerMixin, BaseEstimator
Abstract base class for spatio-temporal crime mapping.
-tfreq (str) – Time frequency (‘M’ for monthly, ‘W’ for weekly, ‘D’ for daily)
grid (GeoDataFrame) – Spatial grid for analysis
start_time (str or datetime, optional) – Analysis start time
end_time (str or datetime, optional) – Analysis end time
Request metadata passed to the fit method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to fit if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to fit.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
x (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for x parameter in fit.
self – The updated object.
-object
-Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
data_points (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for data_points parameter in transform.
self – The updated object.
-object
-Create a grid of points within a given bounding box.
-bbox (GeoDataFrame) – Bounding box as a GeoDataFrame
resolution (float) – Grid cell size in kilometers
return_coords (bool) – If True, returns additional coordinate arrays
debug (bool) – Enable debug printing
Grid points as GeoDataFrame, optionally with coordinate arrays
-GeoDataFrame or tuple
-It constructs a grid of square cells.
-city_shape (GeoDataFrame.) – Corresponds to the boundary geometry in which the grid will be formed.
resolution (float, default is 1.) – Space between the square cells.
Create a hexagonal polygon.
-l (float) – Length of hexagon side
x (float) – X-coordinate of center
y (float) – Y-coordinate of center
Hexagonal polygon
-Polygon
-Dataset Preparation Module
-This module provides functionality for preparing and managing crime datasets along with their -corresponding study areas. It handles spatial data processing and visualization of crime incidents.
-Bases: object
A class to handle crime datasets and their associated study areas.
-crimes (pandas.DataFrame) – DataFrame containing crime data with required columns: -‘tag’, ‘t’ (timestamp), ‘lon’ (longitude), and ‘lat’ (latitude)
study_area (geopandas.GeoDataFrame) – GeoDataFrame defining the study area boundaries
debug (bool, optional) – Enable debug printing. Defaults to False.
Processed crime data with geometry
-geopandas.GeoDataFrame
-Study area boundaries
-geopandas.GeoDataFrame
-Get the crime incidents data.
-The processed crime incidents data
-geopandas.GeoDataFrame
-Plot the study area and crime incidents.
-ax (matplotlib.axes.Axes, optional) – Matplotlib axes for plotting
crime_samples (int, optional) – Number of crime samples to plot. Defaults to 1000
**kwargs – Additional keyword arguments for plotting -study_area: kwargs for study area plot -crimes: kwargs for crime incidents plot
The plot axes
-matplotlib.axes.Axes
-Get the shapes of the dataset components.
-Dictionary containing the shapes of crimes and study_area DataFrames
-dict
-Get the study area boundaries.
-The study area boundaries
-geopandas.GeoDataFrame
-Split the dataset into training and testing sets.
-test_size (float) – Proportion of the dataset to include in the test split. -Must be between 0 and 1. Defaults to 0.25.
-(train_dataset, test_dataset) - Two Dataset objects containing the splits
-tuple
-AssertionError – If test_size is not between 0 and 1
-Feature Engineering Module
-This module provides classes for time series feature engineering and transformation, -including autoregressive features, differencing, seasonality, and trend decomposition.
-Bases: TimeSeriesFeatures
Autoregressive features implementation.
-Apply autoregressive transformation (identity).
-ts (pandas.Series) – Input time series
-Original time series
-pandas.Series
-Feature label for autoregressive features
-str
-Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
stseries (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for stseries parameter in transform.
self – The updated object.
-object
-Bases: TimeSeriesFeatures
Difference features implementation.
-Apply difference transformation.
-ts (pandas.Series) – Input time series
-Differenced time series
-pandas.Series
-Feature label for difference features
-str
-Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
stseries (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for stseries parameter in transform.
self – The updated object.
-object
-Bases: TransformerMixin, BaseEstimator
Feature scaling transformer.
-estimator – Scikit-learn compatible scaling estimator
debug (bool, optional) – Enable debug printing. Defaults to False
Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
x (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for x parameter in transform.
self – The updated object.
-object
-Transform features using the scaling estimator.
-x (pandas.DataFrame) – Input features
-Scaled features
-pandas.DataFrame
-Bases: TimeSeriesFeatures
Seasonal decomposition features implementation.
-Extract seasonal component from time series.
-ts (pandas.Series) – Input time series
-Seasonal component
-pandas.Series
-Feature label for seasonal features
-str
-Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
stseries (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for stseries parameter in transform.
self – The updated object.
-object
-Bases: BaseEstimator, TransformerMixin
Base class for time series feature engineering.
-lags (int) – Number of time lags to use for feature creation
tfreq (str) – Time frequency (‘D’ for daily, ‘W’ for weekly, ‘M’ for monthly)
debug (bool, optional) – Enable debug printing. Defaults to False
AssertionError – If lags is not a positive integer or tfreq is invalid
-Apply time series decomposition.
-ts (pandas.Series) – Input time series
-Transformed time series
-pandas.Series
-Feature label identifier
-str
-Number of time lags
-int
-Create lagged features dataframe.
-ts (pandas.Series) – Input time series
-(lag_df, aligned_ts) - Lagged features and aligned original series
-tuple
-AssertionError – If series length is less than number of lags
-Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
stseries (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for stseries parameter in transform.
self – The updated object.
-object
-Transform the input series into lagged features.
-stseries (pandas.Series) – Input time series with multi-index (time, places)
-Transformed features
-pandas.DataFrame
-Bases: TimeSeriesFeatures
Trend decomposition features implementation.
-Extract trend component from time series.
-ts (pandas.Series) – Input time series
-Trend component
-pandas.Series
-Feature label for trend features
-str
-Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
stseries (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for stseries parameter in transform.
self – The updated object.
-object
-Machine Learning Modelling Module
-This module provides classes for machine learning model pipelines, feature selection, -and prediction functionality for crime density forecasting.
-Bases: TransformerMixin, BaseEstimator
Feature selection transformer.
-estimator – Scikit-learn compatible feature selector
debug (bool, optional) – Enable debug printing. Defaults to False
Fit the feature selector.
-x (pandas.DataFrame) – Input features
y (pandas.Series, optional) – Target variable
The fitted instance
-self
-Request metadata passed to the fit method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to fit if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to fit.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
x (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for x parameter in fit.
self – The updated object.
-object
-Request metadata passed to the transform method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to transform if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to transform.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
x (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for x parameter in transform.
self – The updated object.
-object
-Transform features using the feature selector.
-x (pandas.DataFrame) – Input features
-Selected features
-pandas.DataFrame
-Bases: RegressorMixin, BaseEstimator
Model wrapper for crime density prediction.
-estimator – Scikit-learn compatible regression estimator
debug (bool, optional) – Enable debug printing. Defaults to False
Fit the regression model.
-x (pandas.DataFrame) – Input features
y (pandas.Series, optional) – Target variable
The fitted instance
-self
-Make predictions using the fitted model.
-x (pandas.DataFrame) – Input features
-Predictions with ‘crime_density’ column
-pandas.DataFrame
-Request metadata passed to the fit method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to fit if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to fit.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
x (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for x parameter in fit.
self – The updated object.
-object
-Request metadata passed to the predict method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to predict if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to predict.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
x (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for x parameter in predict.
self – The updated object.
-object
-Request metadata passed to the score method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to score if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to score.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
sample_weight (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for sample_weight parameter in score.
self – The updated object.
-object
-Bases: RegressorMixin, BaseEstimator
Complete pipeline for crime density prediction.
-mapping – Spatial mapping transformer
fextraction – Feature extraction transformer
estimator – Scikit-learn compatible pipeline or estimator
debug (bool, optional) – Enable debug printing. Defaults to False
Current dataset being used
-Dataset
-Evaluate model performance using time series cross-validation.
-scoring (str) – Scoring metric (‘r2’ or ‘mse’)
cv (int) – Number of cross-validation folds
Scores for each fold
-list
-Exception – If scoring metric is invalid
-Get feature importance scores.
-Feature importance scores
-pandas.DataFrame
-Exception – If model hasn’t been fitted or doesn’t support feature importances
-Fit the complete prediction pipeline.
-dataset – Input dataset containing crimes and study area
y – Ignored, present for scikit-learn compatibility
The fitted instance
-self
-Exception – If fitting fails
-Spatial grid used for mapping
-GeoDataFrame
-Make predictions for the next time step.
-Predictions for next time step
-pandas.DataFrame
-Request metadata passed to the fit method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to fit if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to fit.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
dataset (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for dataset parameter in fit.
self – The updated object.
-object
-Request metadata passed to the score method.
Note that this method is only relevant if
-enable_metadata_routing=True (see sklearn.set_config()).
-Please see User Guide on how the routing
-mechanism works.
The options for each parameter are:
-True: metadata is requested, and passed to score if provided. The request is ignored if metadata is not provided.
False: metadata is not requested and the meta-estimator will not pass it to score.
None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (sklearn.utils.metadata_routing.UNCHANGED) retains the
-existing request. This allows you to change the request for some
-parameters and not others.
Added in version 1.3.
-Note
-This method is only relevant if this estimator is used as a
-sub-estimator of a meta-estimator, e.g. used inside a
-Pipeline. Otherwise it has no effect.
sample_weight (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for sample_weight parameter in score.
self – The updated object.
-object
-Spatio-temporal series
-pandas.Series
-Pipeline Module
-This module provides the main pipeline functionality for crime prediction, -including data loading, preprocessing, and model execution.
-Example
->>> from predspot.pipeline import generate_testdata, run_prediction_pipeline
->>> crime_data, study_area = generate_testdata(10000, '2020-01-01', '2020-12-31')
->>> results = run_prediction_pipeline(crime_data, study_area)
-Evaluate the prediction pipeline using cross-validation.
-pipeline (PredictionPipeline) – Fitted prediction pipeline
scoring (str, optional) – Scoring metric (‘r2’ or ‘mse’). Defaults to ‘r2’
cv (int, optional) – Number of cross-validation folds. Defaults to 5
debug (bool, optional) – Enable debug printing. Defaults to False
Cross-validation scores
-list
-Example
->>> scores = evaluate_pipeline(fitted_pipeline, scoring='r2', cv=5)
-Generate synthetic crime data for testing.
-n_points (int) – Number of crime incidents to generate
start_time (str) – Start date in ‘YYYY-MM-DD’ format
end_time (str) – End date in ‘YYYY-MM-DD’ format
debug (bool, optional) – Enable debug printing. Defaults to False
(crimes_df, study_area_gdf) - Generated crime data and study area
-tuple
-Example
->>> crimes, area = generate_testdata(1000, '2020-01-01', '2020-12-31')
-Run the complete crime prediction pipeline.
-crime_data (pandas.DataFrame) – Crime incident data
study_area (geopandas.GeoDataFrame) – Study area boundaries
crime_tags (list, optional) – List of crime types to include
time_range (list, optional) – Time range as [‘HH:MM’, ‘HH:MM’]
tfreq (str, optional) – Time frequency (‘M’, ‘W’, ‘D’). Defaults to ‘M’
grid_resolution (float, optional) – Spatial grid resolution in km. Defaults to 250
debug (bool, optional) – Enable debug printing. Defaults to False
(predictions, pipeline) - Predicted crime densities and fitted pipeline
-tuple
-ValueError – If input data is invalid or missing required columns
-Utilities Module
-This module provides utility functions and classes for data processing and visualization, -including contour generation, feature union operations, and pandas-specific transformations.
-Bases: FeatureUnion
A FeatureUnion transformer that preserves pandas DataFrames.
-This class extends sklearn’s FeatureUnion to work with pandas DataFrames, -maintaining index alignment and column names.
-Number of parallel jobs
-int
-List of transformer tuples
-list
-Weights for transformers
-dict
-Enable debug printing
-bool
-Fit all transformers and transform the data.
-X (pandas.DataFrame) – Input features
y (array-like, optional) – Target values
**fit_params – Additional fitting parameters
Transformed features
-pandas.DataFrame
-ValueError – If no transformers are provided
-Merge transformed features into a single DataFrame.
-Xs (list) – List of transformed DataFrames
-Merged DataFrame
-pandas.DataFrame
-Transform X separately by each transformer.
-X (pandas.DataFrame) – Input features
-Transformed features
-pandas.DataFrame
-Generate GeoJSON contours from spatial data.
-y (pandas.Series) – Values to contour
bbox (GeoDataFrame) – Bounding box for the contour
resolution (float) – Spatial resolution in kilometers
cmin (float) – Minimum contour value
cmax (float) – Maximum contour value
debug (bool, optional) – Enable debug printing. Defaults to False
GeoJSON representation of the contours
-dict
-AssertionError – If bbox is not a GeoDataFrame
-- Searching for multiple words only shows matches that contain - all words. -
- - - - - - - - -