diff --git a/.gitignore b/.gitignore index d257fe7..4b49ff9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,24 @@ .rope* .idea/ notebooks/.ipynb_checkpoints/ -.DS_Store \ No newline at end of file +.DS_Store +.ipynb_checkpoints/ +*.bak +.eggs/ +*.egg-info/ + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 +__pycache__ \ No newline at end of file diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 0000000..aa65c2b --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,28 @@ +v<1.1.0>, 2018-05-17 + +This release is the first tagged release of pointpats on Github. +Starting from this release, pointpats supports python 3.5 and 3.6 only. + +GitHub stats for 2017/04/22 - 2018/05/17 + +These lists are automatically generated, and may be incomplete or contain duplicates. + + +We closed a total of 9 issues, 4 pull requests and 5 regular issues; +this is the full list (generated with the script +:file:`tools/github_stats.py`): + +Pull Requests (4): + +* :ghpull:`12`: b'chore: libpysal is 3 only now so removing travis tests on python 2' +* :ghpull:`11`: b'try removing conversion and see if this passes' +* :ghpull:`10`: b'updating license' +* :ghpull:`9`: b'Update the Readme file, change the relative URL links to absolute URLs' + +Issues (5): + +* :ghissue:`12`: b'chore: libpysal is 3 only now so removing travis tests on python 2' +* :ghissue:`11`: b'try removing conversion and see if this passes' +* :ghissue:`10`: b'updating license' +* :ghissue:`9`: b'Update the Readme file, change the relative URL links to absolute URLs' +* :ghissue:`8`: b'Relative links to notebooks on pypi landing page are broken' diff --git a/README.rst b/README.rst index d45f0e9..87876f0 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ Point Pattern Analysis in PySAL =============================== -.. image:: https://travis-ci.org/pysal/pointpats.svg +.. image:: https://api.travis-ci.org/pysal/pointpats.svg :target: https://travis-ci.org/pysal/pointpats Statistical analysis of planar point patterns. @@ -9,40 +9,48 @@ This package is part of a `refactoring of PySAL `_. -*************** +************ Introduction -*************** +************ -This `PySAL `_ package is intended to support the statistical analysis of planar point patterns. +This `pointpats `_ package is intended to support the statistical analysis of planar point patterns. It currently works on cartesian coordinates. Users with data in geographic coordinates need to project their data prior to using this module. -*************** +Mimicking parts of the original PySAL api can be done with + +``import pointpats.api as ps`` + +******** Examples -*************** +******** + - `Basic point pattern structure `_ - `Centrography and visualization `_ - `Marks `_ - `Simulation of point processes `_ - `Distance based statistics `_ -*************** +************ Installation -*************** +************ + +Install pointpats by running: -Requirements ------------------- -- libpysal -- descartes +:: -*************** + $ pip install pointpats + +*********** Development -*************** +*********** + pointpats development is hosted on `github `_. -As part of the PySAL project, pointpat development follows these `guidelines `_. +As part of the PySAL project, pointpats development follows these `guidelines `_. + +*********** +Bug reports +*********** -*************** -Bug rereports -*************** To search for or report bugs, please see pointpat's `issues `_. diff --git a/pointpats/__init__.py b/pointpats/__init__.py index e43dfff..30fd057 100644 --- a/pointpats/__init__.py +++ b/pointpats/__init__.py @@ -4,3 +4,4 @@ from .centrography import ellipse, mean_center from .process import PointProcess, PoissonPointProcess from .process import PoissonClusterPointProcess +from .version import version as __version__ \ No newline at end of file diff --git a/pointpats/distance_statistics.py b/pointpats/distance_statistics.py index 6e4e572..ff47be1 100644 --- a/pointpats/distance_statistics.py +++ b/pointpats/distance_statistics.py @@ -669,11 +669,11 @@ class Genv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Genv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Genv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) @@ -748,11 +748,11 @@ class Fenv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Fenv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Fenv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) @@ -828,11 +828,11 @@ class Jenv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Jenv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Jenv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) @@ -905,11 +905,11 @@ class Kenv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Kenv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Kenv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) @@ -980,11 +980,11 @@ class Lenv(Envelopes): Examples -------- - >>> import pysal as ps - >>> from points.distance_statistics import Lenv - >>> from pysal.contrib import shapely_ext - >>> from points.process import PoissonPointProcess - >>> from points.window import Window + >>> import libpysal as ps + >>> from pointpats.distance_statistics import Lenv + >>> from libpysal.cg import shapely_ext + >>> from pointpats.process import PoissonPointProcess + >>> from pointpats.window import Window >>> va = ps.open(ps.examples.get_path("vautm17n.shp")) >>> polys = [shp for shp in va] >>> state = shapely_ext.cascaded_union(polys) diff --git a/pointpats/pointpattern.py b/pointpats/pointpattern.py index 496366b..9e3111e 100644 --- a/pointpats/pointpattern.py +++ b/pointpats/pointpattern.py @@ -45,7 +45,7 @@ class PointPattern(object): Examples -------- - >>> from points.pointpattern import PointPattern + >>> from pointpats.pointpattern import PointPattern >>> points = [[66.22, 32.54], [22.52, 22.39], [31.01, 81.21], ... [9.47, 31.02], [30.78, 60.10], [75.21, 58.93], ... [79.26, 7.68], [8.23, 39.93], [98.73, 77.17], @@ -60,9 +60,9 @@ class PointPattern(object): >>> pp.lambda_hull 0.0022667153468973137 >>> pp.hull_area - 5294.0039500000003 + 5294.00395 >>> pp.mbb_area - 7638.2000000000007 + 7638.200000000001 """ def __init__(self, points, window=None, names=None, coord_names=None): diff --git a/pointpats/process.py b/pointpats/process.py index ef3bd4f..94243ec 100644 --- a/pointpats/process.py +++ b/pointpats/process.py @@ -190,10 +190,10 @@ class PoissonPointProcess(PointProcess): Examples -------- - >>> import pysal as ps + >>> import libpysal as ps >>> import numpy as np - >>> from points.window import Window, poly_from_bbox - >>> from pysal.contrib import shapely_ext + >>> from pointpats.window import Window, poly_from_bbox + >>> from libpysal.cg import shapely_ext Open the virginia polygon shapefile >>> va = ps.open(ps.examples.get_path("virginia.shp")) @@ -339,10 +339,10 @@ class PoissonClusterPointProcess(PointProcess): Examples -------- - >>> import pysal as ps + >>> import libpysal as ps >>> import numpy as np - >>> from points.window import Window, poly_from_bbox - >>> from pysal.contrib import shapely_ext + >>> from pointpats.window import Window, poly_from_bbox + >>> from libpysal.cg import shapely_ext Open the virginia polygon shapefile >>> va = ps.open(ps.examples.get_path("virginia.shp")) @@ -433,8 +433,8 @@ def realize(self, n): l, b, r, t = self.window.bbox d = self.radius # get parent points - pxs = np.random.uniform(l, r, (n/self.children, 1)) - pys = np.random.uniform(b, t, (n/self.children, 1)) + pxs = np.random.uniform(l, r, (int(n/self.children), 1)) + pys = np.random.uniform(b, t, (int(n/self.children), 1)) cents = np.hstack((pxs, pys)) # generate children points pnts = [runif_in_circle(self.children, d, center) for center in cents] diff --git a/pointpats/version.py b/pointpats/version.py new file mode 100644 index 0000000..091c17d --- /dev/null +++ b/pointpats/version.py @@ -0,0 +1,6 @@ +import datetime +Major = 1 +Feature = 1 +Bug = 0 +version = '%d.%d.%d' % (Major, Feature, Bug) +stable_release_date = datetime.date(2018, 5, 17) diff --git a/setup.py b/setup.py index d1f57ef..3709c16 100644 --- a/setup.py +++ b/setup.py @@ -3,17 +3,27 @@ from distutils.command.build_py import build_py +with open('README.rst') as file: + long_description = file.read() + pth = os.path.dirname(os.path.abspath(__file__))+ '/requirements.txt' REQUIREMENTS = [i.strip() for i in open(pth).readlines()] +Major = 1 +Feature = 1 +Bug = 0 +version = '%d.%d.%d' % (Major, Feature, Bug) setup(name='pointpats', - version='1.0.1', + version=version, description='Methods and Functions for planar point pattern analysis', + long_description = long_description, url='https://github.com/pysal/pointpats', maintainer='Hu Shao', maintainer_email='shaohutiger@gmail.com', + py_modules=['pointpats'], + python_requires='>3.4', test_suite = 'nose.collector', tests_require=['nose'], keywords='spatial statistics', diff --git a/tools/github_stats.py b/tools/github_stats.py new file mode 100644 index 0000000..7298c55 --- /dev/null +++ b/tools/github_stats.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python +"""Simple tools to query github.com and gather stats about issues. + +Adapted from: https://github.com/ipython/ipython/blob/master/tools/github_stats.py +""" +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from __future__ import print_function + +import json +import re +import sys + +from datetime import datetime, timedelta +from subprocess import check_output +from urllib.request import urlopen +import ssl + +context = ssl._create_unverified_context() + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +ISO8601 = "%Y-%m-%dT%H:%M:%SZ" +PER_PAGE = 100 + +element_pat = re.compile(r'<(.+?)>') +rel_pat = re.compile(r'rel=[\'"](\w+)[\'"]') + +#----------------------------------------------------------------------------- +# Functions +#----------------------------------------------------------------------------- + +def parse_link_header(headers): + link_s = headers.get('link', '') + urls = element_pat.findall(link_s) + rels = rel_pat.findall(link_s) + d = {} + for rel,url in zip(rels, urls): + d[rel] = url + return d + +def get_paged_request(url): + """get a full list, handling APIv3's paging""" + results = [] + while url: + print("fetching %s" % url, file=sys.stderr) + f = urlopen(url, context=context) + results.extend(json.load(f)) + links = parse_link_header(f.headers) + url = links.get('next') + return results + +def get_issues(project="pysal/pointpats", state="closed", pulls=False): + """Get a list of the issues from the Github API.""" + which = 'pulls' if pulls else 'issues' + url = "https://api.github.com/repos/%s/%s?state=%s&per_page=%i" % (project, which, state, PER_PAGE) + return get_paged_request(url) + + +def _parse_datetime(s): + """Parse dates in the format returned by the Github API.""" + if s: + return datetime.strptime(s, ISO8601) + else: + return datetime.fromtimestamp(0) + + +def issues2dict(issues): + """Convert a list of issues to a dict, keyed by issue number.""" + idict = {} + for i in issues: + idict[i['number']] = i + return idict + + +def is_pull_request(issue): + """Return True if the given issue is a pull request.""" + return 'pull_request_url' in issue + + +def issues_closed_since(period=timedelta(days=365), project="pysal/pointpats", pulls=False): + """Get all issues closed since a particular point in time. period +can either be a datetime object, or a timedelta object. In the +latter case, it is used as a time before the present.""" + + which = 'pulls' if pulls else 'issues' + + if isinstance(period, timedelta): + period = datetime.now() - period + url = "https://api.github.com/repos/%s/%s?state=closed&sort=updated&since=%s&per_page=%i" % (project, which, period.strftime(ISO8601), PER_PAGE) + allclosed = get_paged_request(url) + # allclosed = get_issues(project=project, state='closed', pulls=pulls, since=period) + filtered = [i for i in allclosed if _parse_datetime(i['closed_at']) > period] + + # exclude rejected PRs + if pulls: + filtered = [ pr for pr in filtered if pr['merged_at'] ] + + return filtered + + +def sorted_by_field(issues, field='closed_at', reverse=False): + """Return a list of issues sorted by closing date date.""" + return sorted(issues, key = lambda i:i[field], reverse=reverse) + + +def report(issues, show_urls=False): + """Summary report about a list of issues, printing number and title. + """ + # titles may have unicode in them, so we must encode everything below + if show_urls: + for i in issues: + role = 'ghpull' if 'merged_at' in i else 'ghissue' + print('* :%s:`%d`: %s' % (role, i['number'], + i['title'].encode('utf-8'))) + else: + for i in issues: + print('* %d: %s' % (i['number'], i['title'].encode('utf-8'))) + +#----------------------------------------------------------------------------- +# Main script +#----------------------------------------------------------------------------- + +if __name__ == "__main__": + # Whether to add reST urls for all issues in printout. + show_urls = True + + # By default, search one month back + tag = None + if len(sys.argv) > 1: + try: + days = int(sys.argv[1]) + except: + tag = sys.argv[1] + else: + tag = check_output(['git', 'describe', '--abbrev=0']).strip() + + if tag: + cmd = ['git', 'log', '-1', '--format=%ai', tag] + tagday, tz = check_output(cmd).strip().rsplit(' ', 1) + since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S") + else: + since = datetime.now() - timedelta(days=days) + + print("fetching GitHub stats since %s (tag: %s)" % (since, tag), file=sys.stderr) + # turn off to play interactively without redownloading, use %run -i + if 1: + issues = issues_closed_since(since, pulls=False) + pulls = issues_closed_since(since, pulls=True) + + # For regular reports, it's nice to show them in reverse chronological order + issues = sorted_by_field(issues, reverse=True) + pulls = sorted_by_field(pulls, reverse=True) + + n_issues, n_pulls = map(len, (issues, pulls)) + n_total = n_issues + n_pulls + + # Print summary report we can directly include into release notes. + print() + since_day = since.strftime("%Y/%m/%d") + today = datetime.today().strftime("%Y/%m/%d") + print(".. _github-stats:") + print('Github stats') + print('============') + print() + print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag)) + print() + print("These lists are automatically generated, and may be incomplete or contain duplicates.") + print() + if tag: + # print git info, in addition to GitHub info: + since_tag = tag+'..' + cmd = ['git', 'log', '--oneline', since_tag] + ncommits = len(check_output(cmd).splitlines()) + + author_cmd = ['git', 'log', '--format=* %aN', since_tag] + all_authors = check_output(author_cmd).splitlines() + unique_authors = sorted(set(all_authors)) + + print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits)) + print() + print('\n'.join(unique_authors)) + print() + + print() + print("We closed a total of %d issues, %d pull requests and %d regular issues;\n" + "this is the full list (generated with the script \n" + ":file:`tools/github_stats.py`):" % (n_total, n_pulls, n_issues)) + print() + print('Pull Requests (%d):\n' % n_pulls) + report(pulls, show_urls) + print() + print('Issues (%d):\n' % n_issues) + report(issues, show_urls)