traccess 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
traccess-0.0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Willem Klumpenhouwer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.1
2
+ Name: traccess
3
+ Version: 0.0.2
4
+ Summary: Transportation access to opportunities and equity analysis
5
+ Author: Willem Klumpenhouwer
6
+ Author-email: willem@klumpentown.com
7
+ License: MIT
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+
11
+ # Traccess
12
+ Transportation access and equity computations.
13
+
14
+ Traccess offers a set of fast and convenient functions to calculate multiple
15
+ transport accessibility measures. Given a pre-computed travel cost matrix, and
16
+ using data sets on land use supply, demand, and demographics, the package
17
+ computes accessibility levels using multiple accessibility measures, such as:
18
+ cumulative opportunities, minimum travel cost to closest *n* number of activities,
19
+ gravity-based (with different decay functions) and different floating catchment
20
+ area methods.
21
+
22
+ The package also contains a number of different methods for computing
23
+ distributive and sufficientarian equity measures to compare the level of access
24
+ provided across demographic groups.
@@ -0,0 +1,14 @@
1
+ # Traccess
2
+ Transportation access and equity computations.
3
+
4
+ Traccess offers a set of fast and convenient functions to calculate multiple
5
+ transport accessibility measures. Given a pre-computed travel cost matrix, and
6
+ using data sets on land use supply, demand, and demographics, the package
7
+ computes accessibility levels using multiple accessibility measures, such as:
8
+ cumulative opportunities, minimum travel cost to closest *n* number of activities,
9
+ gravity-based (with different decay functions) and different floating catchment
10
+ area methods.
11
+
12
+ The package also contains a number of different methods for computing
13
+ distributive and sufficientarian equity measures to compare the level of access
14
+ provided across demographic groups.
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,24 @@
1
+ [metadata]
2
+ name = traccess
3
+ version = attr: traccess.__version__
4
+ author = Willem Klumpenhouwer
5
+ author_email = willem@klumpentown.com
6
+ description = Transportation access to opportunities and equity analysis
7
+ long_description = file: README.md
8
+ long_description_content_type = text/markdown
9
+ license = MIT
10
+
11
+ [options]
12
+ packages = find:
13
+ install_requires =
14
+ pandas >= 2.0
15
+ package_dir =
16
+ =src
17
+
18
+ [options.packages.find]
19
+ where = src
20
+
21
+ [egg_info]
22
+ tag_build =
23
+ tag_date = 0
24
+
@@ -0,0 +1,3 @@
1
+ import setuptools
2
+
3
+ setuptools.setup()
@@ -0,0 +1,6 @@
1
+ "Python library for computing access to opportunities and equity metrics"
2
+
3
+ __version__ = "0.0.2"
4
+
5
+ from .data import Cost, Demographic, Supply, Access
6
+ from .access import AccessComputer, EquityComputer
@@ -0,0 +1,422 @@
1
+ import numpy
2
+ import pandas
3
+ from typing import Union
4
+
5
+ from .data import Cost, Demand, Demographic, Supply, Access
6
+
7
+
8
+ class AccessComputer:
9
+ """Compute access to destinations."""
10
+
11
+ def __init__(
12
+ self,
13
+ supply: Supply,
14
+ cost: Cost,
15
+ demand: Demand = None,
16
+ ):
17
+ """Compute access to opportunity metrics.
18
+
19
+ The access computer uses supplied datasets on supply, cost, and
20
+ optionally demand to compute popular access to opportunity measures.
21
+
22
+ Parameters
23
+ ----------
24
+ supply : Supply
25
+ The supply or destination set to compute access to
26
+ cost : Cost
27
+ A matrix of cost values (travel time, other cost, etc)
28
+ demand : Demand, optional
29
+ The demand for destinations, used for competitive measures
30
+ """
31
+ if not isinstance(supply, Supply):
32
+ raise TypeError(
33
+ "The supply must be of type Supply. Check the order of arguments passed."
34
+ )
35
+ if not isinstance(cost, Cost):
36
+ raise TypeError(
37
+ "The cost must be of type Cost. Check the order of arguments passed."
38
+ )
39
+ self._supply = supply
40
+ self._cost = cost
41
+ self._demand = demand
42
+
43
+ @property
44
+ def cost(self) -> Cost:
45
+ """Access or set the cost object"""
46
+ return self._cost
47
+
48
+ @cost.setter
49
+ def cost(self, cost: Cost):
50
+ self._cost = cost
51
+
52
+ @property
53
+ def supply(self):
54
+ """Access or set the supply object"""
55
+ return self._supply
56
+
57
+ @supply.setter
58
+ def supply(self, supply: Supply):
59
+ self._supply = supply
60
+
61
+ def cost_to_closest(
62
+ self, cost_column: str, supply_columns: list[str], n=1
63
+ ) -> Access:
64
+ """Compute the cost to the nth closest destination.
65
+
66
+ This function is generic over any kind of numeric travel cost, such as
67
+ distance, time and money.
68
+
69
+ Parameters
70
+ ----------
71
+ cost_column : str
72
+ The column in the Cost object that contains the travel cost
73
+ supply_columns : list[str]
74
+ The columns in the Supply data to compute access to
75
+ n : int, optional
76
+ The nth closest to compute, by default 1
77
+
78
+ Returns
79
+ -------
80
+ Access
81
+ An Access data object with an `id_column` matching the origin zone.
82
+ """
83
+ # First we join the destinations
84
+ with_dest = self.cost.data.reset_index().set_index(self.cost._to_id)
85
+ with_dest = with_dest.join(self.supply.data, how="right")
86
+
87
+ if isinstance(supply_columns, str):
88
+ supply_columns = [supply_columns]
89
+
90
+ columns = []
91
+
92
+ for c in supply_columns:
93
+ # Keep only columns with actual destinations
94
+ this_column = with_dest[with_dest[c] > 0]
95
+
96
+ result = (
97
+ this_column[[self.cost._from_id, cost_column, c]]
98
+ .groupby(self.cost._from_id)
99
+ .apply(_get_nth, o=c, n=n, cost=cost_column)
100
+ )
101
+ columns.append(result)
102
+
103
+ final = pandas.concat(columns, axis="columns", join="outer")
104
+
105
+ final.columns = supply_columns
106
+
107
+ final = final.reset_index()
108
+
109
+ return Access(final, id_column=self.cost._from_id)
110
+
111
+ def cumulative_cutoff(
112
+ self,
113
+ cost_columns: list[str],
114
+ cutoffs: list[float],
115
+ supply_columns: list[str] = None,
116
+ ) -> Access:
117
+ """Compute the total number of opportunities within a specified travel
118
+ cost cutoff.
119
+
120
+ Parameters
121
+ ----------
122
+ cost_columns : list[str]
123
+ A list of cost columns to apply cutoffs to. Can be a single string
124
+ for a single column.
125
+ cutoffs : list[float]
126
+ A list of cutoff values corresponding to each cost column. Can be a
127
+ single value for a single cutoff column. Must be the same length as
128
+ `cost_columns`.
129
+ supply_columns : list[str]
130
+ An optional list of supply columns to use and return. If None, all
131
+ supply columns are used. By default, None.
132
+
133
+ Returns
134
+ -------
135
+ Access
136
+ An Access data object with an `id_column` matching the origin zone.
137
+
138
+ Raises
139
+ ------
140
+ ValueError
141
+ If the supplied column list and cutoff lists are not the same length.
142
+ """
143
+ if isinstance(cost_columns, str):
144
+ cost_columns = [cost_columns]
145
+ if isinstance(cutoffs, float) or isinstance(cutoffs, int):
146
+ cutoffs = [cutoffs]
147
+ if isinstance(supply_columns, str):
148
+ supply_columns = [supply_columns]
149
+
150
+ if len(cost_columns) != len(cutoffs):
151
+ raise ValueError("Cost and cutoff columns must be the same length")
152
+
153
+ join_column = self.cost._to_id
154
+ group_column = self.cost._from_id
155
+
156
+ # Set the join index and join
157
+ df = self.cost.data.reset_index().set_index(join_column)
158
+
159
+ if supply_columns is None:
160
+ supply_columns = self.supply.columns
161
+
162
+ df = df.join(self.supply.data[supply_columns])
163
+
164
+ df["_weights"] = 1.0
165
+ # Iterate through the columns and update weights based on columns
166
+ for idx, c in enumerate(cost_columns):
167
+ df["_weights"] = numpy.where(df[c] <= cutoffs[idx], df["_weights"], 0)
168
+
169
+ # Multily all opportunities by the weights
170
+ df[supply_columns] = df[supply_columns].multiply(df["_weights"], axis="index")
171
+ # Set the group columns
172
+ df.index.rename(join_column, inplace=True)
173
+ df.reset_index(inplace=True)
174
+ # Group and return
175
+ columns = [group_column]
176
+ columns.extend(supply_columns)
177
+ access_df = df[columns].groupby(group_column).sum().reset_index()
178
+ return Access(
179
+ access_df,
180
+ id_column=group_column,
181
+ )
182
+
183
+ def cumulative_decay(self, cost_columns: list[str], decay_function) -> Access:
184
+ pass
185
+
186
+
187
+ class EquityComputer:
188
+ """Compute distributive equity across population demograhics"""
189
+
190
+ def __init__(self, access: Access, demographic: Demographic):
191
+ """Compute distributive equity metrics across population demographics
192
+
193
+ An EquityComputer allows for the combination of access measures and
194
+ demographic data to compute various metrics of distributive equity or
195
+ transportation justice.
196
+
197
+ Parameters
198
+ ----------
199
+ access : Access
200
+ An `Access` object which contains data on access to opportunities.
201
+ demographic : Demographic
202
+ A `Demographic` data object which contains data on demographics and
203
+ populations
204
+ """
205
+ self._access = access
206
+ self._demographic = demographic
207
+
208
+ @property
209
+ def access(self) -> Access:
210
+ return self._access
211
+
212
+ @access.setter
213
+ def access(self, access: Access):
214
+ assert isinstance(access, Access)
215
+ self._access = access
216
+
217
+ @property
218
+ def demographic(self) -> Demographic:
219
+ return self._demographic
220
+
221
+ @demographic.setter
222
+ def demographic(self, demographic: Demographic):
223
+ assert isinstance(demographic, Demographic)
224
+ self._demographic = demographic
225
+
226
+ def in_poverty(
227
+ self, access_column: str, poverty_line: float, is_dual=False
228
+ ) -> pandas.Series:
229
+ """Compute the number of individuals in poverty in any given demographic.
230
+
231
+ Parameters
232
+ ----------
233
+ access_column : str
234
+ The access column to compare the poverty line to
235
+ poverty_line : float
236
+ The poverty line
237
+ is_dual: bool, optional
238
+ Whether the measure is a dual measure, where lower values are
239
+ better, by default False
240
+
241
+ Returns
242
+ -------
243
+ pandas.Series
244
+ A series with each row consisting of a demogrphic group, containing
245
+ the number of that group in poverty.
246
+ """
247
+ # Count the number of people in poverty
248
+ df = self.access.data.join(self.demographic.data)
249
+ if is_dual == True:
250
+ df[df[access_column] > poverty_line][self.demographic.columns].sum()
251
+ else:
252
+ return df[df[access_column] < poverty_line][self.demographic.columns].sum()
253
+
254
+ def fgt_poverty(
255
+ self, access_column: str, poverty_line: float, alpha: float, is_dual=False
256
+ ) -> pandas.Series:
257
+ """Compute a Foster-Greer-Thorbecke (FGT) index for all demographics.
258
+
259
+ FGT measures consider the average amount of poverty in a given
260
+ population by comparing the distance of each individual from the poverty
261
+ line, raised to some exponent alpha. Common values of alpha are 0, 1,
262
+ and 2.
263
+
264
+ When alpha = 0, the function returns the poverty rate
265
+
266
+ When alpha = 1, the function returns the poverty gap index
267
+
268
+ When alpha = 2, the function returns the poverty gap weighted by the
269
+ poverty gap.
270
+
271
+ Parameters
272
+ ----------
273
+ access_column : str
274
+ The access to opportunity column to compare the measure against
275
+ poverty_line : float
276
+ The poverty line to compare the access measure against
277
+ alpha : float
278
+ The alpha parameter, or the extent which to weight those further
279
+ below the poverty line
280
+ is_dual: bool, optional
281
+ Whether the measure is a dual measure, where lower values are
282
+ better, by default False
283
+
284
+ Returns
285
+ -------
286
+ pandas.Series
287
+ A series with an index for each demographic column, containing the
288
+ specified measure.
289
+ """
290
+ df = self.access.data.join(self.demographic.data)
291
+
292
+ # Keep the total of each population group
293
+ n = self.demographic.data.sum().rename("n")
294
+
295
+ if is_dual == True:
296
+ df["_delta"] = df[access_column] - poverty_line
297
+ else:
298
+ df["_delta"] = poverty_line - df[access_column]
299
+
300
+ # We only do the math on the set of those in poverty
301
+ df = df[df["_delta"] > 0]
302
+ df["_delta"] = df["_delta"] / poverty_line
303
+ df["_delta"] = df["_delta"].pow(alpha)
304
+ df[self.demographic.columns] = df[self.demographic.columns].multiply(
305
+ df["_delta"], axis="index"
306
+ )
307
+ totals = df[self.demographic.columns].sum().rename("count")
308
+ totals = pandas.concat([totals, n], axis="columns")
309
+ totals["fgt"] = totals["count"] / totals["n"]
310
+
311
+ return totals["fgt"].rename(f"fgt{alpha}")
312
+
313
+ def poverty_index(
314
+ self, access_column: str, poverty_line: float, is_dual=False
315
+ ) -> pandas.Series:
316
+ """Compute the poverty index at each location.
317
+
318
+ This method computes the poverty index for each location, which is the
319
+ difference between the poverty line and the supplied access value,
320
+ divided by the poverty line.
321
+
322
+ Values above the poverty line are returned as null values.
323
+
324
+ Parameters
325
+ ----------
326
+ access_column : str
327
+ The column to compare the poverty line to
328
+ poverty_line : float
329
+ The poverty line value for access
330
+ is_dual: bool, optional
331
+ Whether the measure is a dual measure, where lower values are
332
+ better, by default False
333
+
334
+ Returns
335
+ -------
336
+ pandas.Series
337
+ A pandas Series containing the poverty index for each location
338
+ """
339
+ df = self.access.data.copy()
340
+ if is_dual == True:
341
+ df["poverty_index"] = (df[access_column] - poverty_line) / poverty_line
342
+ else:
343
+ df["poverty_index"] = (poverty_line - df[access_column]) / poverty_line
344
+ df["poverty_index"] = numpy.where(
345
+ df.poverty_index < 0, pandas.NA, df.poverty_index
346
+ )
347
+ return df["poverty_index"]
348
+
349
+ def weighted_average(self, access_column: str) -> pandas.Series:
350
+ """Compute the population group-weighted average access for all groups.
351
+
352
+ Parameters
353
+ ----------
354
+ access_column : str
355
+ The access value to weight
356
+
357
+ Returns
358
+ -------
359
+ pandas.Series
360
+ A series with a row for demographic group, with the weighted average
361
+ access.
362
+ """
363
+ df = self.access.data.join(self.demographic.data)
364
+ # Normalize the population columns
365
+ for c in self.demographic.columns:
366
+ df[c] = df[c] / df[c].sum()
367
+ # Multiply and sum
368
+ df[self.demographic.columns] = df[self.demographic.columns].multiply(
369
+ df[access_column], axis="index"
370
+ )
371
+ df = df[self.demographic.columns].sum()
372
+ df = df.rename(access_column)
373
+ return df
374
+
375
+ def weighted_quantile(
376
+ self, access_column: str, quantile=0.5, is_dual=False
377
+ ) -> pandas.Series:
378
+ """Compute a population-weighted quantile for all demographics.
379
+
380
+ Population-weighted quantiles are *not interpolated*, meaning that the
381
+ values returned by this function are the highest value in the dataset
382
+ not exceeding the quantile value.
383
+
384
+ Parameters
385
+ ----------
386
+ access_column : str
387
+ The column to compute the quantile over
388
+ quantile : float, optional
389
+ The quantile to use, by default 0.5
390
+ is_dual: bool, optional
391
+ Whether the measure is a dual measure, where lower values are
392
+ better, by default False
393
+
394
+ Returns
395
+ -------
396
+ pandas.Series
397
+ A series containing each demographic group and the access value of
398
+ that quantile.
399
+ """
400
+
401
+ if is_dual == True:
402
+ quantile = 1.0 - quantile
403
+
404
+ df = self.access.data.join(self.demographic.data)
405
+ df.sort_values(access_column, inplace=True)
406
+
407
+ result = dict()
408
+ for c in self.demographic.columns:
409
+ total_weight = df[c].sum()
410
+ df["_cumulative"] = df[c].cumsum()
411
+ result[c] = df[df["_cumulative"] <= (total_weight) * quantile][
412
+ access_column
413
+ ].iloc[-1]
414
+
415
+ return pandas.Series(result, name=f"q{int(quantile * 100)}")
416
+
417
+
418
+ def _get_nth(df, o, n, cost):
419
+ df = df.sort_values(by=cost, ascending=True, na_position="last")
420
+ df["_cumsum"] = df[o].cumsum()
421
+ df = df[df["_cumsum"] >= n]
422
+ return df[cost].min()
@@ -0,0 +1,210 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ import pandas
4
+
5
+
6
+ class AbstractDataSet(ABC):
7
+ """An abstract data set
8
+
9
+ Parameters
10
+ ----------
11
+ dataframe : pandas.DataFrame
12
+ The dataframe containing the data
13
+ id_column : str, optional
14
+ The column which should be used as a unique ID, by default "id"
15
+ """
16
+
17
+ def __init__(self, dataframe: pandas.DataFrame, id_column="id"):
18
+ self._data = dataframe.copy()
19
+ self._data.set_index(id_column, inplace=True)
20
+
21
+ self._id = id_column
22
+
23
+ @property
24
+ def columns(self):
25
+ return [i for i in self._data.columns]
26
+
27
+ @property
28
+ def data(self) -> pandas.DataFrame:
29
+ return self._data
30
+
31
+ @data.setter
32
+ def data(self, data: pandas.DataFrame):
33
+ self._data = data
34
+
35
+ def normalize(self, columns=None, min=0.0, max=1.0):
36
+ """Normalize one or more columns between a range
37
+
38
+ This method scales a column based on its minimum
39
+ and maximum values.
40
+
41
+ Parameters
42
+ ----------
43
+ columns : list, optional
44
+ The list of columns to normalise. If None, all columns are used, by default None
45
+ min : float, optional
46
+ The minimum of the scaled range, by default 0
47
+ max : float, optional
48
+ The maximum of the scaled range, by default 1
49
+ """
50
+ if columns == None:
51
+ columns = self._data.columns
52
+
53
+ for c in columns:
54
+ self._data[c] = (self._data[c] - self._data[c].min()) / (
55
+ self._data[c].max() - self._data[c].min()
56
+ ) * (max - min) + min
57
+
58
+ @classmethod
59
+ def from_csv(cls, csv_filepath, id_column="id", **kwargs):
60
+ """Create a Supply object from a csv file
61
+
62
+ Parameters
63
+ ----------
64
+ csv_filepath : str or path
65
+ The filepath of the CSV to parse
66
+ id_column : str, optional
67
+ The column used as the reference id, by default "id"
68
+
69
+ Returns
70
+ -------
71
+ Supply
72
+ A supply object containing opportunities or land use data
73
+ """
74
+ dataframe = pandas.read_csv(csv_filepath, **kwargs)
75
+ return cls(dataframe, id_column)
76
+
77
+
78
+ class AbstractMatrix(ABC):
79
+ """A representation of a matrix object.
80
+
81
+ Parameters
82
+ ----------
83
+ dataframe : pandas.DataFrame
84
+ The dataframe for the matrix object
85
+ from_id : str, optional
86
+ The first reference id (the origin), by default "from_id"
87
+ to_id : str, optional
88
+ The second reference id (the destination), by default "to_id"
89
+ """
90
+
91
+ def __init__(
92
+ self, dataframe: pandas.DataFrame, from_id="from_id", to_id="to_id"
93
+ ) -> None:
94
+ self._data = dataframe.copy()
95
+ self._data.set_index([from_id, to_id], inplace=True)
96
+
97
+ self._from_id = from_id
98
+ self._to_id = to_id
99
+
100
+ @property
101
+ def columns(self) -> list:
102
+ """The list of columns in the dataset"""
103
+ return self._data.columns
104
+
105
+ @property
106
+ def data(self) -> pandas.DataFrame:
107
+ """The object's dataframe"""
108
+ return self._data
109
+
110
+ @classmethod
111
+ def from_csv(cls, csv_filepath, from_id="from_id", to_id="to_id", **kwargs):
112
+ """Load data and create an object from a CSV file
113
+
114
+ Parameters
115
+ ----------
116
+ csv_filepath : str
117
+ The filepath of the CSV file to load
118
+ from_id : str, optional
119
+ The column name of the origin, by default "from_id"
120
+ to_id : str, optional
121
+ The column name of the destination, by default "to_id"
122
+
123
+ Returns
124
+ -------
125
+ AbstractMatrix
126
+ A matrix dataset.
127
+ """
128
+ dataframe = pandas.read_csv(csv_filepath, **kwargs)
129
+ return cls(dataframe, from_id, to_id)
130
+
131
+ @classmethod
132
+ def from_parquet(cls, parquet_filepath, from_id="from_id", to_id="to_id", **kwargs):
133
+ """Load data and create an object from a Parquet file
134
+
135
+ Parameters
136
+ ----------
137
+ csv_filepath : str
138
+ The filepath of the Parquet file to load
139
+ from_id : str, optional
140
+ The column name of the origin, by default "from_id"
141
+ to_id : str, optional
142
+ The column name of the destination, by default "to_id"
143
+
144
+ Returns
145
+ -------
146
+ AbstractMatrix
147
+ A matrix dataset.
148
+ """
149
+ dataframe = pandas.read_parquet(parquet_filepath, **kwargs)
150
+ return cls(dataframe, from_id, to_id)
151
+
152
+
153
+ class Access(AbstractDataSet):
154
+ pass
155
+
156
+
157
+ class Cost(AbstractMatrix):
158
+ def quantile(self, quantile: float, use_to_id=False) -> pandas.DataFrame:
159
+ """Compute the quantile cost values across all origins or destinations
160
+
161
+ Parameters
162
+ ----------
163
+ quantile : float
164
+ The quantile to compute (0 to 1)
165
+ use_to_id : bool, optional
166
+ If true, group by the destination column instead of the origin, by default False
167
+
168
+ Returns
169
+ -------
170
+ pandas.DataFrame
171
+ A dataframe with an index for each id and the quantile value
172
+ """
173
+ if use_to_id:
174
+ return self.data.groupby(self._to_id).quantile(quantile)
175
+ else:
176
+ return self.data.groupby(self._from_id).quantile(quantile)
177
+
178
+ def median(self, use_to_id=False) -> pandas.DataFrame:
179
+ """Compute the median cost values across all origins or destinations.
180
+
181
+ This function is a shorthand for `Cost.quantile(quantile=0.5)`
182
+
183
+ Parameters
184
+ ----------
185
+ use_to_id : bool, optional
186
+ If true, group by the destination column instead of the origin, by default False
187
+
188
+ Returns
189
+ -------
190
+ pandas.DataFrame
191
+ A dataframe with an index for each id and a median value
192
+ """
193
+ return self.quantile(0.5, use_to_id)
194
+
195
+
196
+ class Demand(AbstractDataSet):
197
+ pass
198
+
199
+
200
+ class Supply(AbstractDataSet):
201
+ def generalized_cost(self):
202
+ raise NotImplementedError
203
+
204
+ def intrazonal(self):
205
+ raise NotImplementedError
206
+
207
+
208
+ class Demographic(AbstractDataSet):
209
+ def something(self):
210
+ raise NotImplementedError
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.1
2
+ Name: traccess
3
+ Version: 0.0.2
4
+ Summary: Transportation access to opportunities and equity analysis
5
+ Author: Willem Klumpenhouwer
6
+ Author-email: willem@klumpentown.com
7
+ License: MIT
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+
11
+ # Traccess
12
+ Transportation access and equity computations.
13
+
14
+ Traccess offers a set of fast and convenient functions to calculate multiple
15
+ transport accessibility measures. Given a pre-computed travel cost matrix, and
16
+ using data sets on land use supply, demand, and demographics, the package
17
+ computes accessibility levels using multiple accessibility measures, such as:
18
+ cumulative opportunities, minimum travel cost to closest *n* number of activities,
19
+ gravity-based (with different decay functions) and different floating catchment
20
+ area methods.
21
+
22
+ The package also contains a number of different methods for computing
23
+ distributive and sufficientarian equity measures to compare the level of access
24
+ provided across demographic groups.
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.cfg
5
+ setup.py
6
+ src/traccess/__init__.py
7
+ src/traccess/access.py
8
+ src/traccess/data.py
9
+ src/traccess/exception.py
10
+ src/traccess.egg-info/PKG-INFO
11
+ src/traccess.egg-info/SOURCES.txt
12
+ src/traccess.egg-info/dependency_links.txt
13
+ src/traccess.egg-info/requires.txt
14
+ src/traccess.egg-info/top_level.txt
15
+ tests/test_access.py
16
+ tests/test_cost.py
17
+ tests/test_supply.py
@@ -0,0 +1 @@
1
+ pandas>=2.0
@@ -0,0 +1 @@
1
+ traccess
@@ -0,0 +1,11 @@
1
+ import traccess
2
+
3
+
4
+ class TestAccessComputer:
5
+ def test_cumulative_cutoff(self, access_object: traccess.AccessComputer):
6
+ df = access_object.cumulative_cutoff(access_object.cost.columns, [29, 2]).data
7
+ assert df.loc[1]["oj"] == 16.0
8
+
9
+ def test_cost_to_closest(self, access_object: traccess.AccessComputer):
10
+ df = access_object.cost_to_closest("c", ["oj2"], n=2).data
11
+ assert df.loc[3]["oj2"] == 20.0
@@ -0,0 +1,7 @@
1
+ import traccess
2
+
3
+
4
+ class TestCost:
5
+ def test_load_from_csv(self, cost_csv_filepath):
6
+ cost = traccess.Cost.from_csv(cost_csv_filepath, "o", "d")
7
+ assert cost.data.loc[(1, 3), "c"] == 20
@@ -0,0 +1,8 @@
1
+ import traccess
2
+
3
+
4
+ class TestSupply:
5
+ def test_load_from_csv(self, supply_csv_filepath):
6
+ supply = traccess.Supply.from_csv(supply_csv_filepath, id_column="dd")
7
+ assert supply.columns[0] == "oj"
8
+ assert supply.data.loc[2]["oj2"] == 1