huff 1.3.2__tar.gz → 1.3.4__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.
- {huff-1.3.2 → huff-1.3.4}/PKG-INFO +2 -18
- {huff-1.3.2 → huff-1.3.4}/huff/gistools.py +2 -2
- {huff-1.3.2 → huff-1.3.4}/huff/models.py +134 -9
- {huff-1.3.2 → huff-1.3.4}/huff/ors.py +2 -2
- {huff-1.3.2 → huff-1.3.4}/huff/osm.py +3 -3
- {huff-1.3.2 → huff-1.3.4}/huff/tests/tests_huff.py +12 -4
- {huff-1.3.2 → huff-1.3.4}/huff.egg-info/PKG-INFO +2 -18
- {huff-1.3.2 → huff-1.3.4}/setup.py +1 -1
- {huff-1.3.2 → huff-1.3.4}/MANIFEST.in +0 -0
- {huff-1.3.2 → huff-1.3.4}/README.md +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/__init__.py +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/__init__.py +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach.cpg +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach.dbf +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach.prj +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach.qmd +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach.shp +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach.shx +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach_supermarkets.cpg +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach_supermarkets.dbf +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach_supermarkets.prj +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach_supermarkets.qmd +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach_supermarkets.shp +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Haslach_supermarkets.shx +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff/tests/data/Wieland2015.xlsx +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff.egg-info/SOURCES.txt +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff.egg-info/dependency_links.txt +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff.egg-info/requires.txt +0 -0
- {huff-1.3.2 → huff-1.3.4}/huff.egg-info/top_level.txt +0 -0
- {huff-1.3.2 → huff-1.3.4}/setup.cfg +0 -0
@@ -1,26 +1,10 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.1
|
2
2
|
Name: huff
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.4
|
4
4
|
Summary: huff: Huff Model Market Area Analysis
|
5
5
|
Author: Thomas Wieland
|
6
6
|
Author-email: geowieland@googlemail.com
|
7
7
|
Description-Content-Type: text/markdown
|
8
|
-
Requires-Dist: geopandas
|
9
|
-
Requires-Dist: pandas
|
10
|
-
Requires-Dist: numpy
|
11
|
-
Requires-Dist: statsmodels
|
12
|
-
Requires-Dist: shapely
|
13
|
-
Requires-Dist: requests
|
14
|
-
Requires-Dist: matplotlib
|
15
|
-
Requires-Dist: pillow
|
16
|
-
Requires-Dist: contextily
|
17
|
-
Requires-Dist: openpyxl
|
18
|
-
Dynamic: author
|
19
|
-
Dynamic: author-email
|
20
|
-
Dynamic: description
|
21
|
-
Dynamic: description-content-type
|
22
|
-
Dynamic: requires-dist
|
23
|
-
Dynamic: summary
|
24
8
|
|
25
9
|
# huff: Huff Model Market Area Analysis
|
26
10
|
|
@@ -4,8 +4,8 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.3.
|
8
|
-
# Last update: 2025-
|
7
|
+
# Version: 1.3.4
|
8
|
+
# Last update: 2025-06-02 17:17
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
@@ -4,8 +4,8 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.3.
|
8
|
-
# Last update: 2025-
|
7
|
+
# Version: 1.3.4
|
8
|
+
# Last update: 2025-06-02 17:17
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
@@ -13,7 +13,9 @@
|
|
13
13
|
import pandas as pd
|
14
14
|
import geopandas as gp
|
15
15
|
import numpy as np
|
16
|
+
from math import sqrt
|
16
17
|
import time
|
18
|
+
from pandas.api.types import is_numeric_dtype
|
17
19
|
from statsmodels.formula.api import ols
|
18
20
|
from shapely.geometry import Point
|
19
21
|
from shapely import wkt
|
@@ -232,6 +234,9 @@ class SupplyLocations:
|
|
232
234
|
|
233
235
|
print("Supply Locations")
|
234
236
|
print("No. locations " + str(metadata["no_points"]))
|
237
|
+
|
238
|
+
if metadata["attraction_col"][0] is None or metadata["attraction_col"] == []:
|
239
|
+
print("Attraction column(s) not defined")
|
235
240
|
else:
|
236
241
|
print("Attraction column(s) " + ",".join(metadata["attraction_col"]))
|
237
242
|
|
@@ -410,8 +415,7 @@ class SupplyLocations:
|
|
410
415
|
|
411
416
|
self.buffers_gdf = buffers_gdf
|
412
417
|
|
413
|
-
return self
|
414
|
-
|
418
|
+
return self
|
415
419
|
|
416
420
|
class InteractionMatrix:
|
417
421
|
|
@@ -442,6 +446,7 @@ class InteractionMatrix:
|
|
442
446
|
|
443
447
|
print("Interaction Matrix")
|
444
448
|
print("----------------------------------")
|
449
|
+
|
445
450
|
print("Supply locations " + str(supply_locations_metadata["no_points"]))
|
446
451
|
if supply_locations_metadata["attraction_col"][0] is None:
|
447
452
|
print("Attraction column not defined")
|
@@ -697,8 +702,6 @@ class InteractionMatrix:
|
|
697
702
|
|
698
703
|
return hansen_df
|
699
704
|
|
700
|
-
=======
|
701
|
-
>>>>>>> e9583d0cd7f349f17f005df0fa75e70160655aa3
|
702
705
|
def mci_transformation(
|
703
706
|
self,
|
704
707
|
cols: list = ["A_j", "t_ij"]
|
@@ -995,6 +998,24 @@ class MCIModel:
|
|
995
998
|
|
996
999
|
return self.market_areas_df
|
997
1000
|
|
1001
|
+
def modelfit(self):
|
1002
|
+
|
1003
|
+
interaction_matrix = self.interaction_matrix
|
1004
|
+
interaction_matrix_df = interaction_matrix.get_interaction_matrix_df()
|
1005
|
+
|
1006
|
+
if ("p_ij" in interaction_matrix_df.columns and "p_ij_emp" in interaction_matrix_df.columns):
|
1007
|
+
|
1008
|
+
mci_modelfit = modelfit(
|
1009
|
+
interaction_matrix_df["p_ij_emp"],
|
1010
|
+
interaction_matrix_df["p_ij"]
|
1011
|
+
)
|
1012
|
+
|
1013
|
+
return mci_modelfit
|
1014
|
+
|
1015
|
+
else:
|
1016
|
+
|
1017
|
+
return None
|
1018
|
+
|
998
1019
|
def summary(self):
|
999
1020
|
|
1000
1021
|
interaction_matrix = self.interaction_matrix
|
@@ -1031,7 +1052,28 @@ class MCIModel:
|
|
1031
1052
|
print (coefficients_df)
|
1032
1053
|
|
1033
1054
|
print("--------------------------------------------")
|
1034
|
-
|
1055
|
+
|
1056
|
+
mci_modelfit = self.modelfit()
|
1057
|
+
if mci_modelfit is not None:
|
1058
|
+
|
1059
|
+
print ("Goodness-of-fit with respect to probabilities")
|
1060
|
+
|
1061
|
+
print("Sum of squared residuals ", round(mci_modelfit[1]["SQR"], 2))
|
1062
|
+
print("Sum of squares ", round(mci_modelfit[1]["SQT"], 2))
|
1063
|
+
print("R-squared ", round(mci_modelfit[1]["Rsq"], 2))
|
1064
|
+
print("Mean squared error ", round(mci_modelfit[1]["MSE"], 2))
|
1065
|
+
print("Root mean squared error ", round(mci_modelfit[1]["RMSE"], 2))
|
1066
|
+
print("Mean absolute error ", round(mci_modelfit[1]["MAE"], 2))
|
1067
|
+
print("Mean absolute percentage error ", round(mci_modelfit[1]["MAPE"], 2))
|
1068
|
+
print("Absolute percentage errors")
|
1069
|
+
print("< 5 % ", round(mci_modelfit[1]["APE"]["resid_below5"], 2))
|
1070
|
+
print("< 10 % ", round(mci_modelfit[1]["APE"]["resid_below10"], 2))
|
1071
|
+
print("< 15 % ", round(mci_modelfit[1]["APE"]["resid_below15"], 2))
|
1072
|
+
print("< 20 % ", round(mci_modelfit[1]["APE"]["resid_below20"], 2))
|
1073
|
+
print("< 25 % ", round(mci_modelfit[1]["APE"]["resid_below25"], 2))
|
1074
|
+
|
1075
|
+
print("--------------------------------------------")
|
1076
|
+
|
1035
1077
|
def utility(
|
1036
1078
|
self,
|
1037
1079
|
transformation = "LCT"
|
@@ -1093,6 +1135,14 @@ class MCIModel:
|
|
1093
1135
|
|
1094
1136
|
interaction_matrix = self.interaction_matrix
|
1095
1137
|
interaction_matrix_df = interaction_matrix.get_interaction_matrix_df()
|
1138
|
+
|
1139
|
+
if "p_ij" in interaction_matrix_df.columns:
|
1140
|
+
interaction_matrix_df["p_ij_emp"] = interaction_matrix_df["p_ij"]
|
1141
|
+
|
1142
|
+
if "U_ij" not in interaction_matrix_df.columns:
|
1143
|
+
self.utility(transformation = transformation)
|
1144
|
+
interaction_matrix = self.interaction_matrix
|
1145
|
+
interaction_matrix_df = interaction_matrix.get_interaction_matrix_df()
|
1096
1146
|
|
1097
1147
|
if interaction_matrix_df["U_ij"].isna().all():
|
1098
1148
|
self.utility(transformation = transformation)
|
@@ -1664,7 +1714,7 @@ def get_isochrones(
|
|
1664
1714
|
|
1665
1715
|
isochrone_gdf[unique_id_col] = unique_id_values[i]
|
1666
1716
|
|
1667
|
-
isochrone_gdf["
|
1717
|
+
isochrone_gdf["segm_min"] = isochrone_gdf["segment"]/60
|
1668
1718
|
|
1669
1719
|
isochrones_gdf = pd.concat(
|
1670
1720
|
[
|
@@ -1677,7 +1727,7 @@ def get_isochrones(
|
|
1677
1727
|
i = i+1
|
1678
1728
|
|
1679
1729
|
isochrones_gdf["segment"] = isochrones_gdf["segment"].astype(int)
|
1680
|
-
isochrones_gdf["
|
1730
|
+
isochrones_gdf["segm_min"] = isochrones_gdf["segm_min"].astype(int)
|
1681
1731
|
|
1682
1732
|
isochrones_gdf.set_crs(
|
1683
1733
|
output_crs,
|
@@ -1692,6 +1742,81 @@ def get_isochrones(
|
|
1692
1742
|
return isochrones_gdf
|
1693
1743
|
|
1694
1744
|
|
1745
|
+
def modelfit(
|
1746
|
+
observed,
|
1747
|
+
expected
|
1748
|
+
):
|
1749
|
+
|
1750
|
+
observed_no = len(observed)
|
1751
|
+
expected_no = len(expected)
|
1752
|
+
|
1753
|
+
if not observed_no == expected_no:
|
1754
|
+
raise ValueError("Observed and expected differ in length")
|
1755
|
+
|
1756
|
+
if not isinstance(observed, np.number):
|
1757
|
+
if not is_numeric_dtype(observed):
|
1758
|
+
raise ValueError("Observed column is not numeric")
|
1759
|
+
if not isinstance(expected, np.number):
|
1760
|
+
if not is_numeric_dtype(expected):
|
1761
|
+
raise ValueError("Expected column is not numeric")
|
1762
|
+
|
1763
|
+
residuals = np.array(observed)-np.array(expected)
|
1764
|
+
residuals_sq = residuals**2
|
1765
|
+
residuals_abs = abs(residuals)
|
1766
|
+
|
1767
|
+
APE = abs(observed-expected)/observed*100
|
1768
|
+
|
1769
|
+
data_residuals = pd.DataFrame({
|
1770
|
+
"observed": observed,
|
1771
|
+
"expected": expected,
|
1772
|
+
"residuals": residuals,
|
1773
|
+
"residuals_sq": residuals_sq,
|
1774
|
+
"residuals_abs": residuals_abs,
|
1775
|
+
"APE": APE
|
1776
|
+
})
|
1777
|
+
|
1778
|
+
SQR = float(np.sum(residuals_sq))
|
1779
|
+
SAR = float(np.sum(residuals_abs))
|
1780
|
+
observed_mean = float(np.sum(observed)/observed_no)
|
1781
|
+
SQT = float(np.sum((observed-observed_mean)**2))
|
1782
|
+
Rsq = float(1-(SQR/SQT))
|
1783
|
+
MSE = float(SQR/observed_no)
|
1784
|
+
RMSE = float(sqrt(MSE))
|
1785
|
+
MAE = float(SAR/observed_no)
|
1786
|
+
MAPE = float(np.mean(APE))
|
1787
|
+
|
1788
|
+
resid_below5 = float(len([APE < 5])/expected_no*100)
|
1789
|
+
resid_below10 = float(len([APE < 10])/expected_no*100)
|
1790
|
+
resid_below15 = float(len([APE < 15])/expected_no*100)
|
1791
|
+
resid_below20 = float(len([APE < 20])/expected_no*100)
|
1792
|
+
resid_below25 = float(len([APE < 25])/expected_no*100)
|
1793
|
+
|
1794
|
+
data_lossfunctions = {
|
1795
|
+
"SQR": SQR,
|
1796
|
+
"SAR": SAR,
|
1797
|
+
"SQT": SQT,
|
1798
|
+
"Rsq": Rsq,
|
1799
|
+
"MSE": MSE,
|
1800
|
+
"RMSE": RMSE,
|
1801
|
+
"MAE": MAE,
|
1802
|
+
"MAPE": MAPE,
|
1803
|
+
"APE": {
|
1804
|
+
"resid_below5": resid_below5,
|
1805
|
+
"resid_below10": resid_below10,
|
1806
|
+
"resid_below15": resid_below15,
|
1807
|
+
"resid_below20": resid_below20,
|
1808
|
+
"resid_below25": resid_below25
|
1809
|
+
}
|
1810
|
+
}
|
1811
|
+
|
1812
|
+
modelfit_results = [
|
1813
|
+
data_residuals,
|
1814
|
+
data_lossfunctions
|
1815
|
+
]
|
1816
|
+
|
1817
|
+
return modelfit_results
|
1818
|
+
|
1819
|
+
|
1695
1820
|
def check_vars(
|
1696
1821
|
df: pd.DataFrame,
|
1697
1822
|
cols: list
|
@@ -4,8 +4,8 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.3.
|
8
|
-
# Last update: 2025-
|
7
|
+
# Version: 1.3.4
|
8
|
+
# Last update: 2025-06-01 17:18
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
@@ -4,8 +4,8 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.3.
|
8
|
-
# Last update: 2025-
|
7
|
+
# Version: 1.3.4
|
8
|
+
# Last update: 2025-06-02 17:18
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
@@ -198,7 +198,7 @@ def map_with_basemap(
|
|
198
198
|
color_mapping = layer_color[color_key]
|
199
199
|
|
200
200
|
if color_key not in layer_3857.columns:
|
201
|
-
raise KeyError ("Column"
|
201
|
+
raise KeyError ("Column " + color_key + " not in layer.")
|
202
202
|
|
203
203
|
for value, color in color_mapping.items():
|
204
204
|
|
@@ -4,13 +4,13 @@
|
|
4
4
|
# Author: Thomas Wieland
|
5
5
|
# ORCID: 0000-0001-5168-9846
|
6
6
|
# mail: geowieland@googlemail.com
|
7
|
-
# Version: 1.3.
|
8
|
-
# Last update: 2025-
|
7
|
+
# Version: 1.3.4
|
8
|
+
# Last update: 2025-06-02 17:17
|
9
9
|
# Copyright (c) 2025 Thomas Wieland
|
10
10
|
#-----------------------------------------------------------------------
|
11
11
|
|
12
12
|
|
13
|
-
from huff.models import create_interaction_matrix, get_isochrones, load_geodata, load_interaction_matrix
|
13
|
+
from huff.models import create_interaction_matrix, get_isochrones, load_geodata, load_interaction_matrix, modelfit
|
14
14
|
from huff.osm import map_with_basemap
|
15
15
|
from huff.gistools import buffers, point_spatial_join
|
16
16
|
|
@@ -179,6 +179,14 @@ Wieland2015_fit = Wieland2015_interaction_matrix.mci_fit(
|
|
179
179
|
Wieland2015_fit.summary()
|
180
180
|
# MCI model summary
|
181
181
|
|
182
|
+
Wieland2015_fit.probabilities()
|
183
|
+
|
184
|
+
Wieland2015_fit_interactionmatrix = Wieland2015_fit.get_interaction_matrix_df()
|
185
|
+
# Export interaction matrix
|
186
|
+
|
187
|
+
Wieland2015_fit.summary()
|
188
|
+
# MCI model summary
|
189
|
+
|
182
190
|
|
183
191
|
# Buffer analysis:
|
184
192
|
|
@@ -240,7 +248,7 @@ map_with_basemap(
|
|
240
248
|
styles={
|
241
249
|
0: {
|
242
250
|
"color": {
|
243
|
-
"
|
251
|
+
"segm_min": {
|
244
252
|
"3": "green",
|
245
253
|
"6": "yellow",
|
246
254
|
"9": "orange",
|
@@ -1,26 +1,10 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.1
|
2
2
|
Name: huff
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.4
|
4
4
|
Summary: huff: Huff Model Market Area Analysis
|
5
5
|
Author: Thomas Wieland
|
6
6
|
Author-email: geowieland@googlemail.com
|
7
7
|
Description-Content-Type: text/markdown
|
8
|
-
Requires-Dist: geopandas
|
9
|
-
Requires-Dist: pandas
|
10
|
-
Requires-Dist: numpy
|
11
|
-
Requires-Dist: statsmodels
|
12
|
-
Requires-Dist: shapely
|
13
|
-
Requires-Dist: requests
|
14
|
-
Requires-Dist: matplotlib
|
15
|
-
Requires-Dist: pillow
|
16
|
-
Requires-Dist: contextily
|
17
|
-
Requires-Dist: openpyxl
|
18
|
-
Dynamic: author
|
19
|
-
Dynamic: author-email
|
20
|
-
Dynamic: description
|
21
|
-
Dynamic: description-content-type
|
22
|
-
Dynamic: requires-dist
|
23
|
-
Dynamic: summary
|
24
8
|
|
25
9
|
# huff: Huff Model Market Area Analysis
|
26
10
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|