diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0ab6186b..0c3f5646 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -19,7 +19,7 @@ jobs: unittests: env: - FETCH_DATA: python -c 'from libpysal.examples import load_example as ex; [ex(e) for e in ["columbus", "desmith", "sids2", "stl"]]' + FETCH_DATA: python -c 'from libpysal.examples import load_example as ex; [ex(e) for e in ["columbus", "desmith", "sids2", "stl", "Sacramento1"]]' RUN_TEST: pytest esda -v -r a -n auto --cov esda --cov-report xml --color yes --cov-append --cov-report term-missing name: CI (${{ matrix.os }}, ${{ matrix.environment-file }}) runs-on: ${{ matrix.os }} diff --git a/esda/moran.py b/esda/moran.py index 0d9a102b..9b2421e7 100644 --- a/esda/moran.py +++ b/esda/moran.py @@ -2,6 +2,7 @@ Moran's I Spatial Autocorrelation Statistics """ + __author__ = ( "Sergio J. Rey , " "Dani Arribas-Bel , " @@ -13,7 +14,8 @@ import numpy as np import pandas as pd import scipy.stats as stats -from libpysal.weights.spatial_lag import lag_spatial as slag +from libpysal.weights import W +from libpysal.weights.spatial_lag import lag_spatial from matplotlib import colors from scipy import sparse @@ -36,6 +38,23 @@ PERMUTATIONS = 999 +def _slag(w, y): + """Helper to compute lag either for W or for Graph""" + if isinstance(w, W): + return lag_spatial(w, y) + else: + return w.lag(y) + + +def _transform(w, transformation): + """Helper to transform W or Graph""" + if isinstance(w, W): + w.transform = transformation + return w + else: + return w.transform(transformation) + + class Moran: """Moran's I Global Autocorrelation Statistic @@ -44,8 +63,8 @@ class Moran: y : array variable measured across n spatial units - w : W - spatial weights instance + w : W | Graph + spatial weights instance as W or Graph aligned with y transformation : string weights transformation, default is row-standardized "r". Other options include "B": binary, "D": @@ -62,7 +81,7 @@ class Moran: ---------- y : array original variable - w : W + w : W | Graph original w object permutations : int number of permutations @@ -161,7 +180,7 @@ def __init__( ): y = np.asarray(y).flatten() self.y = y - w.transform = transformation + w = _transform(w, transformation) self.w = w self.permutations = permutations self.__moments() @@ -212,9 +231,15 @@ def __moments(self): self.EI = -1.0 / (self.n - 1) n = self.n n2 = n * n - s1 = self.w.s1 - s0 = self.w.s0 - s2 = self.w.s2 + if isinstance(self.w, W): + s1 = self.w.s1 + s0 = self.w.s0 + s2 = self.w.s2 + else: + self.summary = self.w.summary() + s1 = self.summary.s1 + s0 = self.summary.s0 + s2 = self.summary.s2 s02 = s0 * s0 v_num = n2 * s1 - n * s2 + 3 * s02 v_den = (n - 1) * (n + 1) * s02 @@ -235,9 +260,10 @@ def __moments(self): self.seI_rand = VIR ** (1 / 2.0) def __calc(self, z): - zl = slag(self.w, z) + zl = _slag(self.w, z) inum = (z * zl).sum() - return self.n / self.w.s0 * inum / self.z2ss + s0 = self.w.s0 if isinstance(self.w, W) else self.summary.s0 + return self.n / s0 * inum / self.z2ss @property def _statistic(self): @@ -257,9 +283,9 @@ def by_col( a pandas dataframe with a geometry column cols : string or list of string name or list of names of columns to use to compute the statistic - w : pysal weights object - a weights object aligned with the dataframe. If not provided, this - is searched for in the dataframe's metadata + w : W | Graph + spatial weights instance as W or Graph aligned with the dataframe. If not + provided, this is searched for in the dataframe's metadata inplace : bool a boolean denoting whether to operate on the dataframe inplace or to return a series contaning the results of the computation. If @@ -304,8 +330,8 @@ class Moran_BV: x-axis variable y : array wy will be on y axis - w : W - weight instance assumed to be aligned with y + w : W | Graph + spatial weights instance as W or Graph aligned with x and y transformation : {'R', 'B', 'D', 'U', 'V'} weights transformation, default is row-standardized "r". Other options include @@ -323,7 +349,7 @@ class Moran_BV: original x variable standardized by mean and std zy : array original y variable standardized by mean and std - w : W + w : W | Graph original w object permutation : int number of permutations @@ -410,7 +436,7 @@ def __init__(self, x, y, w, transformation="r", permutations=PERMUTATIONS): self.zy = zy n = x.shape[0] self.den = n - 1.0 # zx'zx = zy'zy = n-1 - w.transform = transformation + w = _transform(w, transformation) self.w = w self.I = self.__calc(zy) # noqa E741 if permutations: @@ -432,7 +458,7 @@ def __init__(self, x, y, w, transformation="r", permutations=PERMUTATIONS): self.p_z_sim = stats.norm.cdf(self.z_sim) def __calc(self, zy): - wzy = slag(self.w, zy) + wzy = _slag(self.w, zy) self.num = (self.zx * wzy).sum() return self.num / self.den @@ -468,9 +494,9 @@ def by_col( column name or list of column names to use as Y values to compute the bivariate statistic. if no Y is provided, pariwise comparisons among the X variates are used instead. - w : pysal weights object - a weights object aligned with the dataframe. If not provided, this - is searched for in the dataframe's metadata + w : W | Graph + spatial weights instance as W or Graph aligned with the dataframe. If not + provided, this is searched for in the dataframe's metadata inplace : bool a boolean denoting whether to operate on the dataframe inplace or to return a series contaning the results of the computation. If @@ -517,8 +543,8 @@ def Moran_BV_matrix(variables, w, permutations=0, varnames=None): ---------- variables : array or pandas.DataFrame sequence of variables to be assessed - w : W - a spatial weights object + w : W | Graph + spatial weights instance as W or Graph aligned with variables permutations : int number of permutations varnames : list, optional if variables is an array @@ -619,8 +645,8 @@ class Moran_Rate(Moran): b : array a population-at-risk variable measured across n spatial units - w : W - spatial weights instance + w : W | Graph + spatial weights instance as W or Graph aligned with e and b adjusted : boolean whether or not Moran's I needs to be adjusted for rate variable @@ -644,7 +670,7 @@ class Moran_Rate(Moran): rate variable computed from parameters e and b if adjusted is True, y is standardized rates otherwise, y is raw rates - w : W + w : W | Graph original w object permutations : int number of permutations @@ -765,9 +791,9 @@ def by_col( used for all event columns. If more than one population column is provided but there is not a population for every event column, an exception will be raised. - w : pysal weights object - a weights object aligned with the dataframe. If not provided, this - is searched for in the dataframe's metadata + w : W | Graph + spatial weights instance as W or Graph aligned with the dataframe. If not + provided, this is searched for in the dataframe's metadata inplace : bool a boolean denoting whether to operate on the dataframe inplace or to return a series contaning the results of the computation. If @@ -859,8 +885,8 @@ class Moran_Local: ---------- y : array (n,1), attribute array - w : W - weight instance assumed to be aligned with y + w : W | Graph + spatial weights instance as W or Graph aligned with y transformation : {'R', 'B', 'D', 'U', 'V'} weights transformation, default is row-standardized "r". Other options include @@ -898,7 +924,7 @@ class Moran_Local: y : array original variable - w : W + w : W | Graph original w object permutations : int number of random permutations for calculation of pseudo p_values @@ -1023,7 +1049,7 @@ def __init__( z /= sy np.seterr(**orig_settings) self.z = z - w.transform = transformation + w = _transform(w, transformation) self.w = w self.permutations = permutations self.den = (z * z).sum() @@ -1069,11 +1095,11 @@ def __init__( self.p_z_sim = np.nan def __calc(self, w, z): - zl = slag(w, z) + zl = _slag(w, z) return self.n_1 * self.z * zl / self.den def __quads(self): - zl = slag(self.w, self.z) + zl = _slag(self.w, self.z) zp = self.z > 0 lp = zl > 0 pp = zp * lp @@ -1148,9 +1174,9 @@ def by_col( a pandas dataframe with a geometry column cols : string or list of string name or list of names of columns to use to compute the statistic - w : pysal weights object - a weights object aligned with the dataframe. If not provided, this - is searched for in the dataframe's metadata + w : W | Graph + spatial weights instance as W or Graph aligned with the dataframe. If not + provided, this is searched for in the dataframe's metadata inplace : bool a boolean denoting whether to operate on the dataframe inplace or to return a series contaning the results of the computation. If @@ -1220,9 +1246,7 @@ def explore(self, gdf, crit_value=0.05, **kwargs): """ gdf = gdf.copy() gdf["Moran Cluster"] = self.get_cluster_labels(crit_value) - return _explore_local_moran( - self, gdf, crit_value, **kwargs - ) + return _explore_local_moran(self, gdf, crit_value, **kwargs) class Moran_Local_BV: @@ -1235,8 +1259,8 @@ class Moran_Local_BV: x-axis variable y : array (n,1), wy will be on y axis - w : W - weight instance assumed to be aligned with y + w : W | Graph + spatial weights instance as W or Graph aligned with y transformation : {'R', 'B', 'D', 'U', 'V'} weights transformation, default is row-standardized "r". Other options include @@ -1275,7 +1299,7 @@ class Moran_Local_BV: original x variable standardized by mean and std zy : array original y variable standardized by mean and std - w : W + w : W | Graph original w object permutations : int number of random permutations for calculation of pseudo p_values @@ -1368,7 +1392,7 @@ def __init__( np.seterr(**orig_settings) self.zx = zx self.zy = zy - w.transform = transformation + w = _transform(w, transformation) self.w = w self.permutations = permutations self.den = (zx * zx).sum() @@ -1406,11 +1430,11 @@ def __init__( self.p_z_sim = stats.norm.sf(np.abs(self.z_sim)) def __calc(self, w, zx, zy): - zly = slag(w, zy) + zly = _slag(w, zy) return self.n_1 * self.zx * zly / self.den def __quads(self): - zl = slag(self.w, self.zy) + zl = _slag(self.w, self.zy) zp = self.zx > 0 lp = zl > 0 pp = zp * lp @@ -1453,9 +1477,9 @@ def by_col( column name or list of column names to use as Y values to compute the bivariate statistic. if no Y is provided, pariwise comparisons among the X variates are used instead. - w : pysal weights object - a weights object aligned with the dataframe. If not provided, this - is searched for in the dataframe's metadata + w : W | Graph + spatial weights instance as W or Graph aligned with the dataframe. If not + provided, this is searched for in the dataframe's metadata inplace : bool a boolean denoting whether to operate on the dataframe inplace or to return a series contaning the results of the computation. If @@ -1502,8 +1526,8 @@ class Moran_Local_Rate(Moran_Local): (n,1), an event variable across n spatial units b : array (n,1), a population-at-risk variable across n spatial units - w : W - weight instance assumed to be aligned with y + w : W | Graph + spatial weights instance as W or Graph aligned with y adjusted : boolean whether or not local Moran statistics need to be adjusted for rate variable @@ -1543,7 +1567,7 @@ class Moran_Local_Rate(Moran_Local): rate variables computed from parameters e and b if adjusted is True, y is standardized rates otherwise, y is raw rates - w : W + w : W | Graph original w object permutations : int number of random permutations for calculation of pseudo @@ -1666,9 +1690,9 @@ def by_col( used for all event columns. If more than one population column is provided but there is not a population for every event column, an exception will be raised. - w : pysal weights object - a weights object aligned with the dataframe. If not provided, this - is searched for in the dataframe's metadata + w : W | Graph + spatial weights instance as W or Graph aligned with the dataframe. If not + provided, this is searched for in the dataframe's metadata inplace : bool a boolean denoting whether to operate on the dataframe inplace or to return a series contaning the results of @@ -1782,25 +1806,22 @@ def _explore_local_moran(moran_local, gdf, crit_value, **kwargs): } colors5 = [colors5_mpl[i] for i in y] # for mpl hmap = colors.ListedColormap(colors5) - if 'cmap' not in kwargs: - kwargs['cmap'] = hmap + if "cmap" not in kwargs: + kwargs["cmap"] = hmap - m = gdf[["Moran Cluster", "p-value", "geometry"]].explore( - "Moran Cluster", **kwargs - ) + m = gdf[["Moran Cluster", "p-value", "geometry"]].explore("Moran Cluster", **kwargs) return m def _get_cluster_labels(moran_local, crit_value): - gdf = pd.DataFrame() gdf["q"] = moran_local.q gdf["p_sim"] = moran_local.p_sim gdf["Moran Cluster"] = "Insignificant" - gdf.loc[ - (gdf["p_sim"] < crit_value) & (gdf["q"] == 1), "Moran Cluster" - ] = "High-High" + gdf.loc[(gdf["p_sim"] < crit_value) & (gdf["q"] == 1), "Moran Cluster"] = ( + "High-High" + ) gdf.loc[(gdf["p_sim"] < crit_value) & (gdf["q"] == 2), "Moran Cluster"] = "Low-High" gdf.loc[(gdf["p_sim"] < crit_value) & (gdf["q"] == 3), "Moran Cluster"] = "Low-Low" gdf.loc[(gdf["p_sim"] < crit_value) & (gdf["q"] == 4), "Moran Cluster"] = "High-Low" diff --git a/esda/tests/test_moran.py b/esda/tests/test_moran.py index 2ddb318b..c35d4009 100644 --- a/esda/tests/test_moran.py +++ b/esda/tests/test_moran.py @@ -1,41 +1,92 @@ import unittest +import geopandas as gpd import libpysal import numpy as np -import geopandas as gpd import pandas as pd - +import pytest from libpysal.common import ATOL, RTOL - from numpy.testing import assert_array_equal from .. import moran SEED = 12345 - -class Moran_Tester(unittest.TestCase): - def setUp(self): - self.w = libpysal.io.open(libpysal.examples.get_path("stl.gal")).read() +parametrize_stl = pytest.mark.parametrize( + "w", + [ + libpysal.io.open(libpysal.examples.get_path("stl.gal")).read(), + libpysal.graph.Graph.from_W( + libpysal.io.open(libpysal.examples.get_path("stl.gal")).read() + ), + ], + ids=["W", "Graph"], +) +parametrize_sids = pytest.mark.parametrize( + "w", + [ + libpysal.io.open(libpysal.examples.get_path("sids2.gal")).read(), + libpysal.graph.Graph.from_W( + libpysal.io.open(libpysal.examples.get_path("sids2.gal")).read() + ), + ], + ids=["W", "Graph"], +) + +parametrize_lat3x3 = pytest.mark.parametrize( + "w", + [ + libpysal.weights.util.lat2W(3, 3), + libpysal.graph.Graph.from_W(libpysal.weights.util.lat2W(3, 3)), + ], + ids=["W", "Graph"], +) +parametrize_desmith = pytest.mark.parametrize( + "w", + [ + libpysal.io.open(libpysal.examples.get_path("desmith.gal")).read(), + libpysal.graph.Graph.from_W( + libpysal.io.open(libpysal.examples.get_path("desmith.gal")).read() + ), + ], + ids=["W", "Graph"], +) + +sac1 = libpysal.examples.load_example("Sacramento1") +sac1 = gpd.read_file(sac1.get_path("sacramentot2.shp")) + +parametrize_sac = pytest.mark.parametrize( + "w", + [ + libpysal.weights.Queen.from_dataframe(sac1), + libpysal.graph.Graph.build_contiguity(sac1, rook=False), + ], + ids=["W", "Graph"], +) + + +class TestMoran: + def setup_method(self): f = libpysal.io.open(libpysal.examples.get_path("stl_hom.txt")) self.y = np.array(f.by_col["HR8893"]) - def test_moran(self): - mi = moran.Moran(self.y, self.w, two_tailed=False) + @parametrize_stl + def test_moran(self, w): + mi = moran.Moran(self.y, w, two_tailed=False) np.testing.assert_allclose(mi.I, 0.24365582621771659, rtol=RTOL, atol=ATOL) - self.assertAlmostEqual(mi.p_norm, 0.00013573931385468807) + np.testing.assert_allclose(mi.p_norm, 0.00013573931385468807) - def test_sids(self): - w = libpysal.io.open(libpysal.examples.get_path("sids2.gal")).read() + @parametrize_sids + def test_sids(self, w): f = libpysal.io.open(libpysal.examples.get_path("sids2.dbf")) SIDR = np.array(f.by_col("SIDR74")) mi = moran.Moran(SIDR, w, two_tailed=False) np.testing.assert_allclose(mi.I, 0.24772519320480135, atol=ATOL, rtol=RTOL) - self.assertAlmostEqual(mi.p_norm, 5.7916539074498452e-05) + np.testing.assert_allclose(mi.p_norm, 5.7916539074498452e-05) - def test_variance(self): + @parametrize_lat3x3 + def test_variance(self, w): y = np.arange(1, 10) - w = libpysal.weights.util.lat2W(3, 3) mi = moran.Moran(y, w, transformation="B") np.testing.assert_allclose( mi.VI_rand, 0.059687500000000004, atol=ATOL, rtol=RTOL @@ -44,10 +95,11 @@ def test_variance(self): mi.VI_norm, 0.053125000000000006, atol=ATOL, rtol=RTOL ) - def test_z_consistency(self): - m1 = moran.Moran(self.y, self.w) + @parametrize_stl + def test_z_consistency(self, w): + m1 = moran.Moran(self.y, w) # m2 = moran.Moran_BV(self.x, self.y, self.w) TODO testing for other.z values - m3 = moran.Moran_Local(self.y, self.w, keep_simulations=True, seed=SEED) + m3 = moran.Moran_Local(self.y, w, keep_simulations=True, seed=SEED) # m4 = moran.Moran_Local_BV(self.x, self.y, self.w) np.testing.assert_allclose(m1.z, m3.z, atol=ATOL, rtol=RTOL) @@ -61,20 +113,20 @@ def test_by_col(self): sidr = np.unique(mi.SIDR74_moran.values).item() pval = np.unique(mi.SIDR74_p_sim.values).item() np.testing.assert_allclose(sidr, 0.24772519320480135, atol=ATOL, rtol=RTOL) - self.assertAlmostEqual(pval, 0.001) + np.testing.assert_allclose(pval, 0.001) -class Moran_Rate_Tester(unittest.TestCase): - def setUp(self): - self.w = libpysal.io.open(libpysal.examples.get_path("sids2.gal")).read() +class TestMoranRate: + def setup_method(self): f = libpysal.io.open(libpysal.examples.get_path("sids2.dbf")) self.e = np.array(f.by_col["SID79"]) self.b = np.array(f.by_col["BIR79"]) - def test_moran_rate(self): - mi = moran.Moran_Rate(self.e, self.b, self.w, two_tailed=False) + @parametrize_sids + def test_moran_rate(self, w): + mi = moran.Moran_Rate(self.e, self.b, w, two_tailed=False) np.testing.assert_allclose(mi.I, 0.16622343552567395, rtol=RTOL, atol=ATOL) - self.assertAlmostEqual(mi.p_norm, 0.004191499504892171) + np.testing.assert_allclose(mi.p_norm, 0.004191499504892171) @unittest.skip("This function is being deprecated in the next release.") def test_by_col(self): @@ -87,47 +139,44 @@ def test_by_col(self): sidr = np.unique(mi["SID79-BIR79_moran_rate"].values).item() pval = np.unique(mi["SID79-BIR79_p_sim"].values).item() np.testing.assert_allclose(sidr, 0.16622343552567395, rtol=RTOL, atol=ATOL) - self.assertAlmostEqual(pval, 0.008) + np.testing.assert_allclose(pval, 0.008) -class Moran_BV_matrix_Tester(unittest.TestCase): - def setUp(self): +class TestMoranBVmatrix: + def setup_method(self): f = libpysal.io.open(libpysal.examples.get_path("sids2.dbf")) varnames = ["SIDR74", "SIDR79", "NWR74", "NWR79"] self.names = varnames vars = [np.array(f.by_col[var]) for var in varnames] self.vars = vars - self.w = libpysal.io.open(libpysal.examples.get_path("sids2.gal")).read() - def test_Moran_BV_matrix(self): - res = moran.Moran_BV_matrix(self.vars, self.w, varnames=self.names) - self.assertAlmostEqual(res[(0, 1)].I, 0.19362610652874668) - self.assertAlmostEqual(res[(3, 0)].I, 0.37701382542927858) + @parametrize_sids + def test_Moran_BV_matrix(self, w): + res = moran.Moran_BV_matrix(self.vars, w, varnames=self.names) + np.testing.assert_allclose(res[(0, 1)].I, 0.19362610652874668) + np.testing.assert_allclose(res[(3, 0)].I, 0.37701382542927858) -class Moran_Local_Tester(unittest.TestCase): - def setUp(self): - self.w = libpysal.io.open(libpysal.examples.get_path("desmith.gal")).read() +class TestMoranLocal: + def setup_method(self): f = libpysal.io.open(libpysal.examples.get_path("desmith.txt")) self.y = np.array(f.by_col["z"]) - def test_Moran_Local(self): + @parametrize_desmith + def test_Moran_Local(self, w): lm = moran.Moran_Local( self.y, - self.w, + w, transformation="r", permutations=99, keep_simulations=True, seed=SEED, ) - self.assertAlmostEqual(lm.z_sim[0], -0.6990291160835514) - self.assertAlmostEqual(lm.p_z_sim[0], 0.24226691753791396) + np.testing.assert_allclose(lm.z_sim[0], -0.6990291160835514) + np.testing.assert_allclose(lm.p_z_sim[0], 0.24226691753791396) - def test_Moran_Local_labels(self): - sac1 = libpysal.examples.load_example("Sacramento1") - sac1 = gpd.read_file(sac1.get_path("sacramentot2.shp")) - - w = libpysal.weights.Queen.from_dataframe(sac1) + @parametrize_sac + def test_Moran_Local_labels(self, w): lm = moran.Moran_Local( sac1.HSG_VAL.values, w, @@ -156,11 +205,8 @@ def test_Moran_Local_labels(self): np.array([277, 82, 38, 3, 3]), ) - def test_Moran_Local_explore(self): - sac1 = libpysal.examples.load_example("Sacramento1") - sac1 = gpd.read_file(sac1.get_path("sacramentot2.shp")) - - w = libpysal.weights.Queen.from_dataframe(sac1) + @parametrize_sac + def test_Moran_Local_explore(self, w): lm = moran.Moran_Local( sac1.HSG_VAL.values, w, @@ -190,18 +236,19 @@ def test_Moran_Local_explore(self): assert out_str.count("#fdae61") == 6 assert out_str.count("#d3d3d3") == 280 - def test_Moran_Local_parallel(self): + @parametrize_desmith + def test_Moran_Local_parallel(self, w): lm = moran.Moran_Local( self.y, - self.w, + w, transformation="r", n_jobs=-1, permutations=99, keep_simulations=True, seed=SEED, ) - self.assertAlmostEqual(lm.z_sim[0], -0.6990291160835514) - self.assertAlmostEqual(lm.p_z_sim[0], 0.24226691753791396) + np.testing.assert_allclose(lm.z_sim[0], -0.6990291160835514) + np.testing.assert_allclose(lm.p_z_sim[0], 0.24226691753791396) @unittest.skip("This function is being deprecated in the next release.") def test_by_col(self): @@ -218,13 +265,14 @@ def test_by_col(self): keep_simulations=True, seed=SEED, ) - self.assertAlmostEqual(lm.z_z_sim[0], -0.6990291160835514) - self.assertAlmostEqual(lm.z_p_z_sim[0], 0.24226691753791396) + np.testing.assert_allclose(lm.z_z_sim[0], -0.6990291160835514) + np.testing.assert_allclose(lm.z_p_z_sim[0], 0.24226691753791396) - def test_local_moments(self): + @parametrize_desmith + def test_local_moments(self, w): lm = moran.Moran_Local( self.y, - self.w, + w, transformation="r", permutations=0, seed=SEED, @@ -288,26 +336,26 @@ def test_local_moments(self): np.testing.assert_allclose(lm.VI, VI, rtol=RTOL, atol=ATOL) -class Moran_Local_BV_Tester(unittest.TestCase): - def setUp(self): - self.w = libpysal.io.open(libpysal.examples.get_path("sids2.gal")).read() +class TestMoranLocalBV: + def setup_method(self): f = libpysal.io.open(libpysal.examples.get_path("sids2.dbf")) self.x = np.array(f.by_col["SIDR79"]) self.y = np.array(f.by_col["SIDR74"]) - def test_Moran_Local_BV(self): + @parametrize_sids + def test_Moran_Local_BV(self, w): lm = moran.Moran_Local_BV( self.x, self.y, - self.w, + w, keep_simulations=True, transformation="r", permutations=99, seed=SEED, ) - self.assertAlmostEqual(lm.Is[0], 1.4649221250620736) - self.assertAlmostEqual(lm.z_sim[0], 1.330673752886702) - self.assertAlmostEqual(lm.p_z_sim[0], 0.09164819151535242) + np.testing.assert_allclose(lm.Is[0], 1.4649221250620736) + np.testing.assert_allclose(lm.z_sim[0], 1.330673752886702) + np.testing.assert_allclose(lm.p_z_sim[0], 0.09164819151535242) @unittest.skip("This function is being deprecated in the next release.") def test_by_col(self): @@ -328,24 +376,24 @@ def test_by_col(self): bvstats = df["SIDR79-SIDR74_moran_local_bv"].values bvz = df["SIDR79-SIDR74_z_sim"].values bvzp = df["SIDR79-SIDR74_p_z_sim"].values - self.assertAlmostEqual(bvstats[0], 1.4649221250620736) - self.assertAlmostEqual(bvz[0], 1.7900932313425777, 5) - self.assertAlmostEqual(bvzp[0], 0.036719462378528744, 5) + np.testing.assert_allclose(bvstats[0], 1.4649221250620736) + np.testing.assert_allclose(bvz[0], 1.7900932313425777, 5) + np.testing.assert_allclose(bvzp[0], 0.036719462378528744, 5) -class Moran_Local_Rate_Tester(unittest.TestCase): - def setUp(self): - self.w = libpysal.io.open(libpysal.examples.get_path("sids2.gal")).read() +class TestMoranLocalRate: + def setup_method(self): f = libpysal.io.open(libpysal.examples.get_path("sids2.dbf")) self.e = np.array(f.by_col["SID79"]) self.b = np.array(f.by_col["BIR79"]) - def test_moran_rate(self): + @parametrize_sids + def test_moran_rate(self, w): lm = moran.Moran_Local_Rate( - self.e, self.b, self.w, transformation="r", permutations=99, seed=SEED + self.e, self.b, w, transformation="r", permutations=99, seed=SEED ) - self.assertAlmostEqual(lm.z_sim[0], 0.02702781851384379, 7) - self.assertAlmostEqual(lm.p_z_sim[0], 0.4892187730835096) + np.testing.assert_allclose(lm.z_sim[0], 0.02702781851384379, 7) + np.testing.assert_allclose(lm.p_z_sim[0], 0.4892187730835096) @unittest.skip("This function is being deprecated in the next release.") def test_by_col(self): @@ -362,29 +410,11 @@ def test_by_col(self): permutations=99, seed=SEED, ) - self.assertAlmostEqual(lm["SID79-BIR79_z_sim"][0], 0.02702781851384379, 7) - self.assertAlmostEqual(lm["SID79-BIR79_p_z_sim"][0], 0.4892187730835096) + np.testing.assert_allclose(lm["SID79-BIR79_z_sim"][0], 0.02702781851384379, 7) + np.testing.assert_allclose(lm["SID79-BIR79_p_z_sim"][0], 0.4892187730835096) def _fetch_map_string(m): out = m._parent.render() out_str = "".join(out.split()) return out_str - - -suite = unittest.TestSuite() -test_classes = [ - Moran_Tester, - Moran_Rate_Tester, - Moran_BV_matrix_Tester, - Moran_Local_Tester, - Moran_Local_BV_Tester, - Moran_Local_Rate_Tester, -] -for i in test_classes: - a = unittest.TestLoader().loadTestsFromTestCase(i) - suite.addTest(a) - -if __name__ == "__main__": - runner = unittest.TextTestRunner() - runner.run(suite)