Coverage for src/crimepy/nibrs/techniques.py: 94%
41 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-30 18:01 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-30 18:01 +0000
1"""Techniques that use NIBRS data in an `amos` project.
3Each technique is added to the `amos` library when crimepy is imported, so
4it can be named in the settings of any `amos` worker:
6* `load_nibrs` (an `amos.Loader`) loads a table of NIBRS data for years,
7 downloading and converting the master files if needed, so a project that
8 starts with it needs no data of its own.
9* `decode_nibrs` (an `amos.Munger`) replaces codes with their labels, for a
10 table that was loaded with `decode = false`.
12```ini
13[general]
14seed = 43
15label = cleared
17[clearance_project]
18clearance_workers = wrangler, explorer, analyst, critic
20[wrangler]
21techniques = load_nibrs, drop_missing
23[load_nibrs_parameters]
24table = incidents
25years = 2021-2023
26states = DE
27```
29Contents:
30 DecodeNibrs: replaces codes with their labels.
31 LoadNibrs: loads a table of NIBRS data for years.
33"""
35from __future__ import annotations
37import dataclasses
38from typing import Any
40import amos
41import pandas as pd
43from .. import utilities
44from . import munge, store, tables
46""" Techniques """
49@dataclasses.dataclass
50class LoadNibrs(amos.Loader):
51 """Loads a table of NIBRS data for years.
53 Parameters (set in the "load_nibrs_parameters" section of the settings):
55 * `years`: the years to load, such as "2022", "2019, 2021", or
56 "2015-2023". Required.
57 * `table`: a table to build (see `crimepy.nibrs.tables`: "incidents",
58 "victims", "offenses", "offenders", or "arrestees") or a table of the
59 master files (such as "agencies" or "group_b_arrests"). Defaults to
60 "incidents".
61 * `states`: postal abbreviations of the states to keep, such as "DE,
62 MD". Defaults to every state.
63 * `columns`: the columns to keep. Defaults to all of them.
64 * `decode`: whether to replace codes with their labels. Defaults to
65 true.
66 * `folder`: the data folder. Defaults to
67 `crimepy.utilities.data_folder()` (the "crime" folder of the data
68 folder of amos's workspace), which is shared by every project.
69 * `label`, `task`, and `groups`: as in `amos`, they default to those in
70 the "general" section.
72 A label or group that is not a column of the loaded table is left for a
73 later technique to make (such as the "count" column of `count_crimes`),
74 so the "general" section can name the label of the finished data.
76 """
78 """ Required Methods """
80 def load(
81 self,
82 source: Any, # noqa: ARG002
83 years: Any = None,
84 table: str = 'incidents',
85 states: Any = None,
86 columns: Any = None,
87 decode: Any = True,
88 folder: Any = None,
89 **kwargs: Any) -> pd.DataFrame:
90 """Returns the table of NIBRS data.
92 Args:
93 source: not used (it is always the FBI's master files).
94 years: the years to load.
95 table: the table to load or build. Defaults to "incidents".
96 states: the states to keep. Defaults to `None`, which keeps every
97 state.
98 columns: the columns to keep. Defaults to `None`, which keeps
99 them all.
100 decode: whether to replace codes with their labels. Defaults to
101 `True`.
102 folder: the data folder. Defaults to `None`, in which case it is
103 `crimepy.utilities.data_folder()`.
104 **kwargs: arguments for the builder of a built table.
106 Raises:
107 ValueError: if no years are given.
109 Returns:
110 The table.
112 """
113 if not utilities.to_years(years):
114 message = (
115 f'set the "years" of {self.name!r} in the '
116 f'"{self.name}_parameters" section of the settings')
117 raise ValueError(message)
118 wanted = utilities.to_names(columns) or None
119 decode = utilities.to_bool(decode)
120 if table in tables.BUILDERS: 120 ↛ 125line 120 didn't jump to line 125 because the condition on line 120 was always true
121 data = tables.build(
122 table, years, states = states, folder = folder,
123 decode = decode, **kwargs).data
124 return data[wanted] if wanted else data
125 return store.load_frame(
126 table, years, states = states, columns = wanted, decode = decode,
127 folder = folder)
129 """ Public Methods """
131 def apply(self, item: amos.Dataset, **kwargs: Any) -> amos.Dataset:
132 """Loads the table, as a dataset that continues `item`.
134 Args:
135 item: the dataset so far, whose seed and history are kept.
136 **kwargs: the parameters (see the class's documentation).
138 Returns:
139 The dataset of the table.
141 """
142 kwargs.pop('source', None)
143 label = kwargs.pop('label', None) or item.label
144 task = kwargs.pop('task', None)
145 groups = utilities.to_names(kwargs.pop('groups', None) or item.groups)
146 data = self.load('nibrs', **kwargs)
147 if label not in data.columns: 147 ↛ 149line 147 didn't jump to line 149 because the condition on line 147 was always true
148 label, task = None, None
149 groups = [g for g in groups if g in data.columns]
150 loaded = amos.Dataset.create(
151 data, label = label, task = task, seed = item.seed,
152 groups = groups or None, use_gpu = item.use_gpu)
153 loaded.history = item.history
154 loaded.record(
155 self.name, source = 'nibrs', rows = len(data),
156 columns = len(data.columns))
157 return loaded
160@dataclasses.dataclass
161class DecodeNibrs(amos.Munger):
162 """Replaces the codes of NIBRS data with their labels.
164 Parameters (set in the "decode_nibrs_parameters" section of the
165 settings):
167 * `columns`: the columns to decode. Defaults to every column of codes
168 (see `crimepy.nibrs.munge.CODE_COLUMNS`).
170 """
172 """ Required Methods """
174 def munge(
175 self,
176 data: pd.DataFrame,
177 columns: Any = None,
178 **kwargs: Any) -> pd.DataFrame:
179 """Replaces codes with their labels.
181 Args:
182 data: a table of NIBRS data with codes.
183 columns: the columns to decode. Defaults to `None`, which decodes
184 every column of codes.
185 **kwargs: not used.
187 Returns:
188 The table, with labels.
190 """
191 names = utilities.to_names(columns) or None
192 return munge.decode(data, names)