Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESDA Tabular Functions #827

Merged
merged 27 commits into from
Jul 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b0b8a2c
add tabular descriptive spatial statistics
ljwolf Jun 21, 2016
bcab9d1
add skeleton of tests
ljwolf Jun 21, 2016
c26a1b0
add shp2series flag in Table.to_df() function
ljwolf Jun 21, 2016
cc3eb29
fix one-letter typo in adding asarray in Moran
ljwolf Jun 21, 2016
20d4c7c
add stats & documentation
ljwolf Jun 21, 2016
7b4469f
adding finalized tabular stuff for esda
ljwolf Jun 24, 2016
c08468c
add work on by_col methods for tabular esda
ljwolf Jun 26, 2016
fd12fe1
fix 825: Headbanging Median Rate Ignores Edge Correction
ljwolf Jun 26, 2016
df3638b
fix 826: Spatial Filtering grid definition
ljwolf Jun 28, 2016
b78ea24
fix unittest import in accessors
ljwolf Jun 28, 2016
16bde13
add overview notebook
ljwolf Jun 28, 2016
f622955
remove cruft in esda and make converters soft import
ljwolf Jun 26, 2016
00ad349
enforce dependency-safety around contrib module
ljwolf Jun 26, 2016
2269c60
time to atone for the rm -rf hack to get py3 working w/ travis
ljwolf Jun 26, 2016
939699d
fix test declaration in accessors
ljwolf Jun 26, 2016
a0a21ba
add inline testing for columnar classmethods
ljwolf Jun 29, 2016
b5f16a8
find_bin should return the same type as yb
ljwolf Jun 29, 2016
6b79bc7
fix ESDA notebook & find_bin return type
ljwolf Jun 29, 2016
f89ff60
add work on building unittests for geotable operations
ljwolf Jun 29, 2016
fd20387
stop clobbering travis test logs w/ deprecation warnings.
ljwolf Jun 29, 2016
2de89d2
add comparators and work to test shapely extension
ljwolf Jun 29, 2016
83cdcce
Add elementwise comparators.
ljwolf Jun 30, 2016
1fe186f
fix 824: Direct Age Standardization fails for empty regions
ljwolf Jun 30, 2016
1b61438
remove cruft in preparation for merge
ljwolf Jun 30, 2016
1040309
add tests for to_df table method
ljwolf Jul 1, 2016
81ab8d3
remove _eq stubs
ljwolf Jul 1, 2016
d7e7121
skip df test if no pandas
ljwolf Jul 1, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ before_install:
- conda create -y -q -n test-env python=$TRAVIS_PYTHON_VERSION
- source activate test-env
- chmod +x ./.travis_testing.sh
- if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then rm -rf pysal/contrib; 2to3 -nw pysal/ > /dev/null; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then 2to3 -nw pysal/ > /dev/null; fi

install:
- conda install --yes numpy scipy nose pip
Expand Down
2 changes: 1 addition & 1 deletion .travis_testing.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
if [[ $TRAVIS_PYTHON_VERSION == 3* ]]; then
cd pysal; python -m 'nose';
nosetests --with-coverage --cover-package=pysal --exclude-dir=pysal/contrib
else
nosetests --with-coverage --cover-package=pysal;
fi
26 changes: 13 additions & 13 deletions pysal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@
import pysal.cg
import pysal.core

try:
import pandas
from pysal.contrib import pdio
pysal.common.pandas = pandas
except ImportError:
print('Pandas adapters not loaded')
pysal.common.pandas = None

# Load the IOHandlers
from pysal.core import IOHandlers
# Assign pysal.open to dispatcher
open = pysal.core.FileIO.FileIO

from pysal.version import version
#from pysal.version import stable_release_date
#import urllib2, json
Expand Down Expand Up @@ -84,19 +97,6 @@
import pysal.examples
from pysal.network.network import Network, NetworkG, NetworkK, NetworkF

try:
import pandas
from pysal.contrib import pdio
pysal.common.pandas = pandas
except ImportError:
print('Pandas adapters not loaded')
pysal.common.pandas = None

# Load the IOHandlers
from pysal.core import IOHandlers
# Assign pysal.open to dispatcher
open = pysal.core.FileIO.FileIO

#__all__=[]
#import esda,weights
#__all__+=esda.__all__
Expand Down
153 changes: 153 additions & 0 deletions pysal/cg/comparators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import numpy as np
from pysal.common import RTOL
from .shapes import Point, Chain, Polygon, Geometry

#### This approach is invalid :(
#### OGC has a different definition of equality, so we can't be ogc-compliant
#### by focusing on vertex set relationships. For example:

# from shapely import geometry as geom
# p1 = geom.Polygon([(0,0),(0,1),(1,0)])
# p2 = geom.Polygon([(0,0),(0,1),(.5,.5),(1,0)])
# p1.equals(p2) #is true
# no relationship on point set (without simplifying boundaries) will make these
# approaches work.

ATOL = 1e-12

def _point_equal(a,b):
"""
Test that a point is exactly equal to another point, using numpy array
comparison
"""
return np.array_equal(a._Point__loc, b._Point__loc)

def _point_almost_equal(a,b, rtol=RTOL, atol=ATOL):
"""
test that one point is equal to another point, up to a specified relative or
absolute tolerance.
"""
return np.allclose(a._Point__loc, b._Point__loc,
rtol=rtol, atol=atol)

def _chain_equal(a,b):
"""
Test that two chains are equal. This considers reversed-incident chains,
chains with the same pointset but in reverse order, as different chains.
"""
for a_part, b_part in zip(a.parts, b.parts):
for a_seg, b_seg in zip(a_part, b_part):
if not np.array_equal(a_seg, b_seg):
return False
return True

def _chain_almost_equal(a,b, rtol=RTOL, atol=ATOL):
"""
Test that two chains are equal, up to a specified relative or absolute
tolerance. This considers reversed-incident chains,
chains with the same pointset but in reverse order, as different chains.
"""
for a_part, b_part in zip(a.parts, b.parts):
for a_seg, b_seg in zip(a_part, b_part):
if not np.allclose(a_seg, b_seg,
rtol=RTOL, atol=ATOL):
return False
return True

def _poly_exactly_equal(a,b):
"""
Test that two polygons are equal. This is a straightfoward linear comparison
of parts and holes. That is, it is assumed that the parts and holes are
conformal.

Thus, shapes with the same parts or holes, but listed in a different order,
will be considered different. Solving this will require some way to relate
ring/hole sets
"""
for a_hole, b_hole in zip(a.holes, b.holes):
if not np.array_equal(a_hole, b_hole):
return False
for a_part, b_part in zip(a.parts, b.parts):
if not np.array_equal(a_part, b_part):
return False
return True

def _poly_exactly_coincident(a,b):
"""
Check that two polygons have coincident boundaries

Thus, this returns True when two polygons have the same holes & parts in any
order with potentially-reversed path directions
"""
n_holes = len(a.holes)
n_parts = len(a.parts)
if n_holes != len(b.holes):
return False
if n_parts != len(b.parts):
return False
b_in_a = [None]*n_holes
a_in_b = [None]*n_holes
for i, a_hole in a.holes:
for j, b_hole in b.holes:
i_j = coincident(a_hole, b_hole)
if i_j:
b_in_a[j] = True
a_in_b[i] = True
break
if not a_in_b[i]:
return False
if any(in_b):
return False
for b_hole in b.holes:
in_a = [not coincident(b_hole, a_hole) for a_hole in a.holes]
if any(in_a):
return False
return True

def _coincident(a,b):
"""
Check if two vertex lists are equal, either forwards or backwards. Thus,
this checks for equality of the path or equality when one path is reversed.
"""
return np.array_equal(a, b) or np.array_equal(np.flipud(a),b)

def _almost_coincident(a,b, rtol=RTOL, atol=ATOL):
"""
Check if two vertex lists are equal to within a given relative and absolute
tolernance, either forwards or backwards. Thus,
this checks for equality of the path or equality when one path is reversed.
"""
return (np.allclose(a, b, rtol=RTOL, atol=ATOL)
or np.allclose(np.flipud(a),b, rtol=RTOL, atol=ATOL))

def _poly_almost_equal(a,b, rtol=RTOL, atol=ATOL):
is_equal = True
for a_hole, b_hole in zip(a.holes, b.holes):
is_equal &= np.allclose(a_hole, b_hole,
rtol=rtol, atol=atol)
for a_part, b_part in zip(a.parts, b.parts):
is_equal &= np.allclose(a_part, b_part,
rtol=rtol, atol=atol)
return is_equal

_EQ_MAP = {Point:_point_equal,
Polygon:_poly_equal,
Chain: _chain_equal}
_AEQ_MAP = {Point:_point_almost_equal,
Polygon:_poly_almost_equal,
Chain: _chain_almost_equal}

def equal(a,b):
ta, tb = type(a), type(b)
if not all([isinstance(a, tb), isinstance(b, ta)]):
return False
return _EQ_MAP[ta](a,b)

def almost_equal(a,b, rtol=RTOL, atol=ATOL):
ta, tb = type(a), type(b)
if not all([isinstance(a, tb), isinstance(b, ta)]):
return False
return _AEQ_MAP[ta](a,b)

def is_shape(a):
return isinstance(a, Geometry)
26 changes: 17 additions & 9 deletions pysal/cg/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import math
from warnings import warn
from sphere import arcdist
import numpy as np

__all__ = ['Point', 'LineSegment', 'Line', 'Ray', 'Chain', 'Polygon',
'Rectangle', 'asShape']
Expand Down Expand Up @@ -37,8 +38,15 @@ def asShape(obj):
raise NotImplementedError(
"%s is not supported at this time." % geo_type)

class Geometry(object):
"""
A base class to help implement is_geometry and make geometric types
extendable.
"""
def __init__(self):
pass

class Point(object):
class Point(Geometry):
"""
Geometric class for point objects.

Expand Down Expand Up @@ -345,7 +353,7 @@ def __str__(self):
return "POINT ({} {})".format(*self.__loc)


class LineSegment(object):
class LineSegment(Geometry):
"""
Geometric representation of line segment objects.

Expand Down Expand Up @@ -776,7 +784,7 @@ def line(self):
return self._line


class VerticalLine:
class VerticalLine(Geometry):
"""
Geometric representation of verticle line objects.

Expand Down Expand Up @@ -853,7 +861,7 @@ def y(self, x):
return float('nan')


class Line:
class Line(Geometry):
"""
Geometric representation of line objects.

Expand Down Expand Up @@ -982,7 +990,7 @@ def __init__(self, origin, second_p):
self.p = second_p


class Chain(object):
class Chain(Geometry):
"""
Geometric representation of a chain, also known as a polyline.

Expand Down Expand Up @@ -1018,7 +1026,7 @@ def __init__(self, vertices):
else:
self._vertices = [vertices]
self._reset_props()

@classmethod
def __from_geo_interface__(cls, geo):
if geo['type'].lower() == 'linestring':
Expand Down Expand Up @@ -1180,7 +1188,7 @@ def segments(self):
return [[LineSegment(a, b) for (a, b) in zip(part[:-1], part[1:])] for part in self._vertices]


class Ring(object):
class Ring(Geometry):
"""
Geometric representation of a Linear Ring

Expand Down Expand Up @@ -1385,7 +1393,7 @@ def contains_point(self, point):



class Polygon(object):
class Polygon(Geometry):
"""
Geometric representation of polygon objects.

Expand Down Expand Up @@ -1756,7 +1764,7 @@ def contains_point(self, point):



class Rectangle:
class Rectangle(Geometry):
"""
Geometric representation of rectangle objects.

Expand Down
2 changes: 1 addition & 1 deletion pysal/contrib/geotable/ops/_accessors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import functools as _f

__all__ = [ 'area', 'bbox', 'bounding_box', 'centroid', 'holes', 'len',
'parts', 'perimeter', 'segments', 'vertices']
'parts', 'perimeter', 'segments', 'vertices']

def get_attr(df, geom_col='geometry', inplace=False, attr=None):
outval = df[geom_col].apply(lambda x: x.__getattribute__(attr))
Expand Down