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

1"""Techniques that use NIBRS data in an `amos` project. 

2 

3Each technique is added to the `amos` library when crimepy is imported, so 

4it can be named in the settings of any `amos` worker: 

5 

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`. 

11 

12```ini 

13[general] 

14seed = 43 

15label = cleared 

16 

17[clearance_project] 

18clearance_workers = wrangler, explorer, analyst, critic 

19 

20[wrangler] 

21techniques = load_nibrs, drop_missing 

22 

23[load_nibrs_parameters] 

24table = incidents 

25years = 2021-2023 

26states = DE 

27``` 

28 

29Contents: 

30 DecodeNibrs: replaces codes with their labels. 

31 LoadNibrs: loads a table of NIBRS data for years. 

32 

33""" 

34 

35from __future__ import annotations 

36 

37import dataclasses 

38from typing import Any 

39 

40import amos 

41import pandas as pd 

42 

43from .. import utilities 

44from . import munge, store, tables 

45 

46""" Techniques """ 

47 

48 

49@dataclasses.dataclass 

50class LoadNibrs(amos.Loader): 

51 """Loads a table of NIBRS data for years. 

52 

53 Parameters (set in the "load_nibrs_parameters" section of the settings): 

54 

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. 

71 

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. 

75 

76 """ 

77 

78 """ Required Methods """ 

79 

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. 

91 

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. 

105 

106 Raises: 

107 ValueError: if no years are given. 

108 

109 Returns: 

110 The table. 

111 

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) 

128 

129 """ Public Methods """ 

130 

131 def apply(self, item: amos.Dataset, **kwargs: Any) -> amos.Dataset: 

132 """Loads the table, as a dataset that continues `item`. 

133 

134 Args: 

135 item: the dataset so far, whose seed and history are kept. 

136 **kwargs: the parameters (see the class's documentation). 

137 

138 Returns: 

139 The dataset of the table. 

140 

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 

158 

159 

160@dataclasses.dataclass 

161class DecodeNibrs(amos.Munger): 

162 """Replaces the codes of NIBRS data with their labels. 

163 

164 Parameters (set in the "decode_nibrs_parameters" section of the 

165 settings): 

166 

167 * `columns`: the columns to decode. Defaults to every column of codes 

168 (see `crimepy.nibrs.munge.CODE_COLUMNS`). 

169 

170 """ 

171 

172 """ Required Methods """ 

173 

174 def munge( 

175 self, 

176 data: pd.DataFrame, 

177 columns: Any = None, 

178 **kwargs: Any) -> pd.DataFrame: 

179 """Replaces codes with their labels. 

180 

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. 

186 

187 Returns: 

188 The table, with labels. 

189 

190 """ 

191 names = utilities.to_names(columns) or None 

192 return munge.decode(data, names)