diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9aa63d2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: python +python: + - "3.5" + +#command to run tests +script: nosetests diff --git a/pointPattern_Analysis.ipynb b/pointPattern_Analysis.ipynb new file mode 100644 index 0000000..2432329 --- /dev/null +++ b/pointPattern_Analysis.ipynb @@ -0,0 +1,266 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n", + "(-72.976512, 41.337662) [' Thu, Sept. 18th 2014', 'all-cases-dead-on-arrival', ' 1605 WHALLEY AVE', ' AMITY/POND LILY', ' 01:53 p.m.']\n", + "(-72.960621, 41.326316) [' Wed, Sept. 3rd 2014', 'animal-bites', ' 46 FOUNTAIN ST', ' CENTRAL/PHILIP', ' 01:23 p.m.']\n", + "(-72.896278, 41.324366) [' Mon, Sept. 8th 2014', 'animal-bites', ' 1 LYMAN ST', ' WELTON/INTERSECTION', ' 02:33 p.m.']\n", + "(-72.952039, 41.325584) [' Sat, Sept. 20th 2014', 'animal-bites', ' 15 DIAMOND ST', ' BLAKE/RUBY', ' 03:37 p.m.']\n", + "(-72.956456, 41.329289) [' Sat, Sept. 20th 2014', 'animal-bites', ' 26 AUSTIN ST', ' BLAKE/HARD', ' 06:52 p.m.']\n", + "The new_haven_merged dataset has a total average nearest neighbor distance of: 0.000240361352928\n", + "The new_haven_merged dataset with the mark 'animal-bites' mark has a total average nearest neighbor distance of: 0.0126135423802\n", + "The new_haven_merged dataset with the mark 'animal-bites' and 'assault-wdangerous-weapon' marks has a total average nearest neighbor distance of 0.00672269248745\n", + "The new_haven_merged dataset's g function results are:\n", + "0.0\n", + "0.4322805952019435\n", + "0.4828423929547525\n", + "0.4951412086243547\n", + "0.49848162769511084\n", + "0.4992408138475554\n", + "0.49954448830853326\n", + "0.4998481627695111\n", + "0.5\n", + "0.5\n", + "0.5\n", + "0.5\n", + "The new_haven_merged dataset's g function results with a mark of 'animal-bites' are: \n", + "0.0\n", + "0.1875\n", + "0.1875\n", + "0.3125\n", + "0.3125\n", + "0.375\n", + "0.4375\n", + "0.5\n", + "0.5\n", + "0.5\n", + "0.5\n", + "0.5\n", + "[ 0.00511722 0.00779492 0.01047262 0.01315031 0.01582801 0.01850571\n", + " 0.02118341 0.02386111 0.0265388 0.0292165 0.0318942 0.0345719 ]\n", + "[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0051172213163033367, 0.0051172213163033367, 0.0051172213163033367]\n", + "[0.076923076923076927, 0.15384615384615385, 0.15384615384615385, 0.26923076923076922, 0.38461538461538464, 0.38461538461538464, 0.38461538461538464, 0.38461538461538464, 0.42307692307692307, 0.42307692307692307, 0.46153846153846156, 0.5]\n", + "[[ 0. 0.07692308 0.07692308 ..., 0.23076923 0.23076923\n", + " 0.34615385]\n", + " [ 0.00511722 0.00779492 0.01047262 ..., 0.0292165 0.0318942\n", + " 0.0345719 ]\n", + " [ 0. 0. 0. ..., 0.26923077 0.26923077\n", + " 0.38461538]\n", + " ..., \n", + " [ 0.00511722 0.00779492 0.01047262 ..., 0.0292165 0.0318942\n", + " 0.0345719 ]\n", + " [ 0. 0. 0.07692308 ..., 0.23076923 0.26923077\n", + " 0.26923077]\n", + " [ 0.00511722 0.00779492 0.01047262 ..., 0.0292165 0.0318942\n", + " 0.0345719 ]]\n", + "The new_haven_merged dataset's g function results with a marks of 'animal-bites' and 'assault-wdangerous-weapon' are: \n", + "0.0\n", + "0.15789473684210525\n", + "0.2894736842105263\n", + "0.3157894736842105\n", + "0.39473684210526316\n", + "0.42105263157894735\n", + "0.4473684210526316\n", + "0.47368421052631576\n", + "0.5\n", + "0.5\n", + "0.5\n", + "0.5\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\nXTMXhJNu46cAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#from . import pointPattern\n", + "#from . import point\n", + "import pysal as ps\n", + "import unittest\n", + "from src.point import Point\n", + "from src.pointPattern import PointPattern\n", + "import matplotlib\n", + "import matplotlib.pyplot as pyp\n", + "import numpy as np\n", + "%pylab inline\n", + "\n", + "#utilize the example shapefile\n", + "shapefile = ps.open(ps.examples.get_path('new_haven_merged.shp'))\n", + "dbf = ps.open(ps.examples.get_path('new_haven_merged.dbf'))\n", + "i = 0\n", + "for geometry, attributes in zip(shapefile,dbf):\n", + " if i < 5:\n", + " print(geometry,attributes)\n", + " i = i +1\n", + "\n", + "pointList = []\n", + "point_pattern = PointPattern()\n", + "for geometry,attributes in zip(shapefile,dbf):\n", + " #create a list of points to then append to a pointPattern:\n", + " point_pattern.add_point(Point(geometry[0],geometry[1],[attributes[0],attributes[1],attributes[2],attributes[3],attributes[4]])) #third parameters is a list of marks\n", + "\n", + "#so now you have the points part of pointPattern, create an instance of pointPattern:\n", + "\n", + "#add points to your self.points:\n", + "#for p in pointList:\n", + " # point_pattern.add_point(p)\n", + "\n", + "#how many points have a nearest neighbor closer than the distance \"band\" step thing\n", + " #okay, so now you actually have the data inside pointPattern's self.points. Now you can do some analysis:\n", + "\n", + " #illustrate the use of mean nearest neighbor on the entire dataset:\n", + "kd_avg_nn = point_pattern.kDTree_nearest_neighbor()\n", + "print(\"The new_haven_merged dataset has a total average nearest neighbor distance of: \", kd_avg_nn)\n", + "\n", + "#illustrate the use of the mean nearest neighbor on a mark:\n", + "kd_avg_nn_mark = point_pattern.kDTree_nearest_neighbor(['animal-bites'])\n", + "print(\"The new_haven_merged dataset with the mark 'animal-bites' mark has a total average nearest neighbor distance of: \", kd_avg_nn_mark)\n", + "\n", + "kd_avg_nn_mark2 = point_pattern.kDTree_nearest_neighbor(['animal-bites','assault-wdangerous-weapon'])\n", + "print(\"The new_haven_merged dataset with the mark 'animal-bites' and 'assault-wdangerous-weapon' marks has a total average nearest neighbor distance of\", kd_avg_nn_mark2)\n", + "\n", + "\n", + "#ssssssillustrate the use of the g function:\n", + "np_compute_g, ds1 = point_pattern.numpy_compute_g(12)\n", + "print(\"The new_haven_merged dataset's g function results are:\")\n", + "for g in np_compute_g:\n", + " print(g)\n", + "\n", + "np_compute_g_mark, ds2 = point_pattern.numpy_compute_g(12,['animal-bites'])\n", + "print(\"The new_haven_merged dataset's g function results with a mark of 'animal-bites' are: \")\n", + "for g in np_compute_g_mark:\n", + " print(g)\n", + " \n", + "print(ds2)\n", + "\n", + "#find the critical points for the g function,pass in the distance band you want it to use:\n", + "minc,maxc,garray = point_pattern.g_critical_points(ds2)\n", + "print(minc)\n", + "print(maxc)\n", + "print(garray)\n", + "\n", + "np_compute_g_mark2, ds3 = point_pattern.numpy_compute_g(12,['animal-bites','assault-wdangerous-weapon'])\n", + "print(\"The new_haven_merged dataset's g function results with a marks of 'animal-bites' and 'assault-wdangerous-weapon' are: \")\n", + "for g in np_compute_g_mark2:\n", + " print(g)\n", + "\n", + "#now plot the point pattern:\n", + "x = []\n", + "y = []\n", + "#put the points into x and y:\n", + "for p in point_pattern.points:\n", + " x.append(p.x)\n", + " y.append(p.y)\n", + "\n", + "fig, ax1 = pyp.subplots()\n", + "ax1.plot(np.array(x),np.array(y),color='blue',marker='D',linewidth=0)\n", + "ax1.set_xlabel(r'$x$')\n", + "ax1.set_ylabel(r'$y$')\n", + "ax1.set_title('Point Pattern')\n", + "\n", + "\n", + "#now print the results of the g function (with mark animal bites):\n", + "#np_compute_g holds the list of the G function for each discrete d.\n", + "#ds1 holds the distance bands:\n", + "fig2, ax2 = pyp.subplots()\n", + "#ax2.plot(np.array(np_compute_g_mark),np.array(ds2),'ro-')\n", + "ax2.plot(np.array(ds2),np.array(np_compute_g_mark2),'ro-')\n", + "ax2.plot(np.array(ds2),np.array(minc))\n", + "ax2.plot(np.array(ds2),np.array(maxc))\n", + "#ax2.plot((np.amin(ds2),np.amax(ds2)),(criticalList[0],criticalList[0]),color='blue')\n", + "#ax2.plot((np.amin(ds2),np.amax(ds2)),(criticalList[1],criticalList[1]),color='blue')\n", + "#for i in range(12):\n", + "# ax2.plot((minc[i],ds2[i]),color='blue')\n", + "# ax2.plot((maxc[i],ds2[i]),color='blue')\n", + "ax2.set_xlabel(r'$d$')\n", + "ax2.set_ylabel(r'$\\hat{G}(d)$')\n", + "ax2.set_title(r'$\\hat{G}(d)$ Result')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.1" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/analytics.py b/src/analytics.py new file mode 100644 index 0000000..342bb91 --- /dev/null +++ b/src/analytics.py @@ -0,0 +1,291 @@ +import math + +def find_largest_city(gj): + """ + Iterate through a geojson feature collection and + find the largest city. Assume that the key + to access the maximum population is 'pop_max'. + + Parameters + ---------- + gj : dict + A GeoJSON file read in as a Python dictionary + + Returns + ------- + city : str + The largest city + + population : int + The population of the largest city + """ + #features is a list, so iteration is by position + #if you want to iterate over the features you need to first grab the list out of the dictionary. + + featureList = gj['features'] + # now that you have the features, compare the pop_max fields to find the largest one + max_population = 0 + for featureEntry in featureList: + if featureEntry["properties"]["pop_max"] > max_population: + max_population = featureEntry["properties"]["pop_max"] + city = featureEntry["properties"]["nameascii"] + + + return city, max_population + +def write_your_own(gj): + """ + This function finds the least populated city, pop_min + """ + featureList = gj["features"] + minPop = math.inf + for featureEntry in featureList: + #feature["properties"]["pop_min"] for feature in self.gj["features"] + if featureEntry["properties"]["pop_min"] < minPop: + minPop = featureEntry["properties"]["pop_min"] + city = featureEntry["properties"]["nameascii"] + # minn = min(featureEntry["properties"]["pop_min"]) +# print(minn) + return city, minPop + +def mean_center(points): + """ + Given a set of points, compute the mean center + + Parameters + ---------- + points : list + A list of points in the form (x,y) + + Returns + ------- + x : float + Mean x coordinate + + y : float + Mean y coordinate + """ + + #find the average of all the X points in the list + + # x_sum = sum(points[0]) + #points_length = len(points) + + sums = map(sum,zip(*points)) # returns iterable object of type map + sumsL = list(sums) + avgs = map(lambda xy: xy/len(points),sumsL) + avgsL = list(avgs) + x = avgsL[0] + y = avgsL[1] + + return x,y + +def average_nearest_neighbor_distance(points,mark=None): + """ + Given a set of points, compute the average nearest neighbor. + + Parameters + ---------- + points : list + A list of Points in the form of (x,y,mark) or points with (x,y) + + Returns + ------- + mean_d : float + Average nearest neighbor distance + + References + ---------- + Clark and Evan (1954 Distance to Nearest Neighbor as a + Measure of Spatial Relationships in Populations. Ecology. 35(4) + p. 445-453. + """ + markList = [] + if not mark: #If mark is empty, then you're computing the distance of all the points + shDistL =[] #list of shortest distances + + #now the points are numbered... so if the points + #have the same counter number attached also, then they + #are self-neighbors, but if num1 != num2, then they are + # coincident points, with distance = 0 + printXonce = False + for num1, point in enumerate(points): + shortestDistance = math.inf + for num2, dpoint in enumerate(points): + if num1 != num2: + if printXonce == False: + print(point.x) + epoint1 = (point.x,point.y) + epoint2 = (dpoint.x,dpoint.y) + dist = utils.euclidean_distance(epoint1, epoint2) #changed input parameters because cannot pass in Point + if(shortestDistance > dist): + shortestDistance = dist + printXonce = True + #now add the shortest distance of that point before it moves on to a new point + shDistL.append(shortestDistance) + # print(shDistL) + sums = sum(shDistL) + mean_d = sums/len(shDistL) + #compute the average nearest neighbor distance of only those that share the mark + else: + for p in points: + if p.mark in mark: #passed in a list of possible marks + markList.append(p) + shDistL =[] #list of shortest distances + + #now the points are numbered... so if the points + #have the same counter number attached also, then they + #are self-neighbors, but if num1 != num2, then they are + # coincident points, with distance = 0 + for num1, point in enumerate(markList): + shortestDistance = math.inf + for num2, dpoint in enumerate(markList): + if num1 != num2: + dist = utils.euclidean_distance((point.x,point.y), (dpoint.x,dpoint.y)) + if(shortestDistance > dist): + shortestDistance = dist + #now add the shortest distance of that point before it moves on to a new point + shDistL.append(shortestDistance) + #print(shDistL) + sums = sum(shDistL) + mean_d = sums/len(shDistL) + print(mean_d) + return mean_d + + +def minimum_bounding_rectangle(points): + """ + Given a set of points, compute the minimum bounding rectangle. + + Parameters + ---------- + points : list + A list of points in the form (x,y) + + Returns + ------- + : list + Corners of the MBR in the form [xmin, ymin, xmax, ymax] + """ + # a minimum bounding rectangle would be on the extremes of x/y + + xmin = math.inf + ymin = math.inf + xmax = -9999999999 + ymax = -9999999999 + for point in points: + if point[0] < xmin: + xmin = point[0] + if point[1] < ymin: + ymin = point[1] + if point[0] > xmax: + xmax = point[0] + if point[1] > ymax: + ymax = point[1] + mbr = [xmin,ymin,xmax,ymax] + print("This is the mbr:") + print(mbr) + return mbr + +def mbr_area(mbr): + """ + Compute the area of a minimum bounding rectangle + """ + length = mbr[2] - mbr[0] + width = mbr[3] - mbr[1] + area = length*width + + return area + +def expected_distance(area, n): + """ + Compute the expected mean distance given + some study area. + + This makes lots of assumptions and is not + necessarily how you would want to compute + this. This is just an example of the full + analysis pipe, e.g. compute the mean distance + and the expected mean distance. + + Parameters + ---------- + area : float + The area of the study area + + n : int + The number of points + """ + + expected = 0.5 * (math.sqrt(area/n)) + return expected + + +def permutation_nearest_distance(mark=[],p=99,n=100): + """ + Finds the nearest neighbor distance for p permutations with n + random points + :param p: permutation number of times you want to try different + simulations for monte carlo + :param n: random point number + :param mark: Passes in a list of marks if the permutation to be found is of Points + :return LDist: list of distances, length p + """ + # if mark == None: + # LDist = [] + # for x in range(p): #loop from 0 to p + # #create n random Points + # points = n_random_points(n) # returns [(x,y),(a,b)..] + # #compute mean neighbor distance +# mean_d = average_nearest_neighbor_distance(points) + # LDist.append(mean_d) + + LDist = [] + for x in range(p): #loop from 0 to p + #create n random Points + marks = ['lavender','orange','rose','ash','violet','magenta','cerulean'] + points = utils.n_random_Points(n,marks) # returns [(x,y),(a,b)..] + #print("print the points array: ") + #print(points) + #print(type(points)) + #compute mean neighbor distance + mean_d = average_nearest_neighbor_distance(points) + LDist.append(mean_d) + + return LDist + +def critical_points(LDist): + """ + Find the critical points, the largest/smallest distances + :param LDist: the list of mean distances + :return CList: list containing critical points + """ + CList = [] + #aList = [] + #for p in LDist: + # print(p) + smallest = min(LDist) + largest = max(LDist) + CList.append(smallest) + CList.append(largest) + #print(CList) + return CList + +def significant(CList,distance): + """ + Returns True if the observed distance is significant + :param CList: list of critical points + :param distance: the observed distance + :return result: True/False + """ + + if distance < CList[0] or distance > CList[1]: + result = True + else: + result = False + return result + + +import math + +from . import utils \ No newline at end of file diff --git a/src/point.py b/src/point.py new file mode 100644 index 0000000..135a28e --- /dev/null +++ b/src/point.py @@ -0,0 +1,35 @@ +class Point(object): + def __init__(self,x,y,mark=[]): + self.x = x + self.y = y + self.mark = mark + + #implement magic method to add points (x,y)'s + def __add__(self,other): + return Point(self.x + other.x,self.y + other.y) + def __radd__(self,other): + return Point(self.x + other, self.y + other) + def __sub__(self, other): #implements self - other + return Point(self.x - other.x,self.y - other.y) + def __eq__(self,other): + return self.x == other.x and self.y == other.y + + def __neq__(self,other): + return self.x != other.x or self.y != other.y + + def patched_coincident(self,point2): + point1 = (self.x,self.y) + + return utils.check_coincident(point1,point2) + + def patched_shift(self,x_shift,y_shift): + point = (self.x,self.y) + self.x,self.y = utils.shift_point(point,x_shift,y_shift) + + #Add an attribute to your Point class that returns an array [x,y] + def return_array(self): + arrayy = [self.x,self.y] + return arrayy + + +from . import utils diff --git a/src/pointPattern.py b/src/pointPattern.py new file mode 100644 index 0000000..df7c3d1 --- /dev/null +++ b/src/pointPattern.py @@ -0,0 +1,314 @@ +import random +import math +from . import point +from . import analytics +from . import utils +import numpy as np +import scipy.spatial as ss + + +class PointPattern(object): + #initialize to list of points + def __init__(self): + self.points = [] + self.marks = ['lavender','orange','rose','ash','violet','magenta','cerulean'] + + def add_point(self,point): + self.points.append(point) + + def remove_point(self,index): + del(self.points[index]) + + def average_nearest_neighbor(self,mark=None): + return analytics.average_nearest_neighbor_distance(self.points,mark) + + #find the number of coincident points + def coin_count(self): + count = 0 + clist = [] + for num1, point1 in enumerate(self.points): + for num2, point2 in enumerate(self.points): + #check that you're not comparing the same point + if num1 != num2 and num2 not in clist: # if they are not the same point, and already counted + if point1 == point2: + count = count +1 + clist.append(num2) + return count + + #creating a point pattern for each of the 99 times. + def create_pointPattern(self): + point_pat = PointPattern() + #Point(random.uniform(-72.8,-72.999),random.uniform(41.2,41.3999),random.choice(marks)) + for x in range(13): #create 13 points, like the pointPattern you are checking it against + point_pat.add_point(point.Point(random.uniform(-72.8,-72.99),random.uniform(41.2,41.3999))) + + #return the pointPattern that you created + return point_pat + + #list all the different marks + def mark_list(self): + markList = [] + + #go through each point and add a new mark to the mark listss + for point in self.points: + if point.mark not in markList: + markList.append(point.mark) + return markList + + #return a subset of point by mark type + def mark_subset(self,mark): + subset = [] + for p in self.points: + if p.mark == mark: + subset.append(p) + return subset + + #where n is either provided by the user or equal to the current size of pointPattern + def create_n_random_points(self,n =None): + randomPoints = [] + if(n is None): + n = len(self.points) + + for i in range(n): + randomPoints.append(point.Point(random.randint(1,100),random.randint(1,100),random.choice(self.marks))) + + return randomPoints + + #simulate k random points patterns for Monte Carlo + def create_k_patterns(self,k): + return analytics.permutation_nearest_distance(self.marks,k) + + def critical_points(self): + return analytics.critical_points(self.create_k_patterns(99)) + #return analytics.critical_points(self.points) + + def g_critical_points(self,distBands): + #99 times, create a point pattern: + first = True + for y in range(99): + point_pat = PointPattern() + for x in range(13): #create 12 points, like the pointPattern you are checking it against + point_pat.add_point(point.Point(random.uniform(-72.8,-72.99),random.uniform(41.2,41.3999))) + + #once you've created that point pattern, get the g function value of that point pattern: + gList = point_pat.numpy_compute_g(12,None,distBands) #created a list like [1,2,3,4,5,5,66,,7,8], want to make a matrix with that + #create a "matrix" with that stuff: + if first: + g_array = gList + first = False + else: + # g_array = np.vstack(g_array,gList) + g_array = np.vstack((g_array,gList)) + + #after 99 times of that, you have a 99 x 12 matrix, + #find the min and max of each column: + min_arrayi = g_array.argmin(0) + max_arrayi = g_array.argmax(0) + + min_array = [] + max_array = [] + + #now min_array and max_array hold the min and max indices for + #each column. + i = 0 # the column index for g_array: + k = 0 + #i'm given how far down I should go, not how far accross I should go + #I'm given what row the min is on + for minindex in min_arrayi: + #the first time, i =0, and you also want to get stuff from row 0: + #g[i] = [1,2,3,4,5] + min_val = g_array[minindex][i] + min_array.append(min_val) + i = i + i #so the next time, you're go to the minindex row and go to the 1st column + + for maxindex in max_arrayi: + max_val = g_array[maxindex][k] + max_array.append(max_val) + k = k + 1 + + + return min_array,max_array,g_array + + + def compute_g(self,nsteps,mark=None): + """ + + Parameters + ---------- + nsteps: The numer of discrete d that are used to compute G(d) + + Returns + ------- + + """ + point_array = [] + if not mark: + for p in self.points: + point_array.append(p.return_array()) + else: + #that means a mark(s) was passed in + for p in self.points: + if p.mark in mark: + point_array.append(p.return_array()) + shDistL = [] # list keeps track of all the nearest neighbor distances for each point + gFuncL = [] + #for pointPattern range 0-5, the highest distance between them is < 8 + ds = np.linspace(0,8,nsteps) + sums = 0 + N = np.point_array.size() #get a count of how many points there are + for dstep in ds: #for every distance band dstep + for num1, p in enumerate(point_array): + shortestDistance = math.inf + for num2, dp in enumerate(point_array): + if num1 != num2: #if they aren't the same point, find the distance between the two points + p1 = (p.x,p.y) + p2 = (dp.x,dp.y) + dist = utils.euclidean_distance(p1, p2) + if(shortestDistance > dist): + shortestDistance = dist + #now add the shortest distance of that point before it moves on to a new point + shDistL.append(shortestDistance) + #now you have the minimum nearest neighbor distances of each point stored in shDistL. + #Check how many of those distances are less than the current distance band: + for d in shDistL: + count = 0 + if d < dstep: #then it should be included in the count + count = count + 1 + #now I've got the count, compute the g function: + gFuncL.append(count/N) + return gFuncL + + +#utilize a scipy.spatial.KDTree to compute the nearest neighbor distance + + def kDTree_nearest_neighbor(self,mark=None): + point_array = [] # is the "tuple" that holds the arrays to be stacked + if not mark: + #then you dont want to look for a specific mark, and you can just find the average nearest neighbor of everything + for p in self.points: #add every point to the point_array + point_array.append(p.return_array()) + else: + #that means that there was something passed into mark and you have to only computer the nearest neighbor with that mark + for p in self.points: + for m in mark: # passed in a possible list of marks + if m in p.mark: + point_array.append(p.return_array()) + break #so that you don't add duplicates if it has both marks for example + + #now you have the vstack parameter: + point_ndarray = np.vstack(point_array) + + #now you have the ndarray needed for kdtree, create your tree: + kdTree = ss.KDTree(point_array) + + #now computer the nearest neighbors: + nn_dist = [] + for p in point_ndarray: + nearest_neighbor_distance, nearest_neighbor_point = kdTree.query(p,k=2) + nn_dist.append(nearest_neighbor_distance[1]) #appending the second one to allow for self-neighbor + + average = np.mean(nn_dist) + return average + + #compute the nearest neighbor distance using numpy (ndarray and mean) + def numpy_nearest_neighbor(self,mark=None): + shDistL = [] + point_array = [] + if not mark: + for p in self.points: + point_array.append(p.return_array()) + else: + #that means a mark was passed in + for p in self.points: + if mark in p.mark: + point_array.append(p.return_array()) + + #point_array = [ [1,2],[3,4],[5,6],[7.8] ] + #using the same logic that's in analytics: + for num1, p in enumerate(point_array): # p = [1,2] + shortestDistance = math.inf + for num2, dp in enumerate(point_array): + if num1 != num2: + dist = ss.distance.euclidean(p,dp) + if(shortestDistance > dist): + shortestDistance = dist + #now add the shortest distance of that point before it moves on to a new point: + shDistL.append(shortestDistance) + mean_d = np.mean(shDistL) #returns the average of the array of elements, so pass in shDistL + + return mean_d + + #compute the G function using numpy + def numpy_compute_g(self,nsteps,mark=None,distBands=None): + point_array = [] + + if not mark: + for p in self.points: + point_array.append(p.return_array()) + else: + #that means a mark(s) was passed in + for p in self.points: + for m in mark: + if m in p.mark: + point_array.append(p.return_array()) + break # so that you don't add duplicates if it has both marks for example + shDistL = [] # list keeps track of all the nearest neighbor distances for each point + gFuncL = [] + + #first get the nearest neighbor distance for each point: + for num1, p in enumerate(point_array): + shortestDistance = math.inf + for num2, dp in enumerate(point_array): + if num1 != num2: + # print(p) + # print(dp) + # p1 = (p.x,p.y) + # p2 = (dp.x,dp.y) + # dist = utils.euclidean_distance(p1, p2) + dist = ss.distance.euclidean(p,dp) + if(shortestDistance > dist): + shortestDistance = dist + shDistL.append(shortestDistance) + # print("the shortest distance: ",shortestDistance) + #now you have the minimum nearest neighbor distances of each point stored in shDistL. + #use that to compute the steps: + if distBands is None: + min = np.amin(shDistL) + max = np.amax(shDistL)*1.5 #so that the last max distance on the g function will fall under a distance band just a little bit larger. + ds = np.linspace(min,max,nsteps) + else: + ds = distBands + min = np.amin(ds) + max = np.amax(ds) + ds = np.linspace(min,max,nsteps) + N = np.size(point_array) #get a count of how many points there are + + #now calculate the g function for every distance band: + for dstep in ds: + count = np.where(shDistL < dstep) + count = len(count[0]) + #now you have the count of observations under that distance band. + #divide by N and add it to the gFuncL. + gFuncL.append(count/N) + return gFuncL, ds + + + #Generate random points within some domain + def random_points_domain(self,numPoints = 2,start=0,stop=1,seed =None): + randomp = None + pointsList = [] + if seed is None: #meaning no passed in starting value + randomp = np.random + else: + randomp = np.random.RandomState(seed) #instantiate seed + random.seed(seed) + points = randomp.uniform(start,stop, (numPoints,2)) #create ndarray + for x in range(points): #for all the points + pointsList.append(point.Point(points[x][0],points[x][1],np.random.choice(self.marks))) + return pointsList + + + + + + diff --git a/src/pointPattern_Analysis.py b/src/pointPattern_Analysis.py new file mode 100644 index 0000000..48a55dc --- /dev/null +++ b/src/pointPattern_Analysis.py @@ -0,0 +1,64 @@ +#from . import pointPattern +#from . import point +import pysal as ps +import unittest +from src.point import Point +from src.pointPattern import PointPattern + +#utilize the example shapefile +shapefile = ps.open(ps.examples.get_path('new_haven_merged.shp')) +dbf = ps.open(ps.examples.get_path('new_haven_merged.dbf')) +i = 0 +for geometry, attributes in zip(shapefile,dbf): + if i < 5: + print(geometry,attributes) + i = i +1 + +pointList = [] +point_pattern = PointPattern() +for geometry,attributes in zip(shapefile,dbf): + #create a list of points to then append to a pointPattern: + point_pattern.add_point(Point(geometry[0],geometry[1],[attributes[0],attributes[1],attributes[2],attributes[3],attributes[4]])) #third parameters is a list of marks + +#so now you have the points part of pointPattern, create an instance of pointPattern: + +#add points to your self.points: +#for p in pointList: + # point_pattern.add_point(p) + +#how many points have a nearest neighbor closer than the distance "band" step thing + #okay, so now you actually have the data inside pointPattern's self.points. Now you can do some analysis: + + #illustrate the use of mean nearest neighbor on the entire dataset: +#kd_avg_nn = point_pattern.kDTree_nearest_neighbor() +#print("The new_haven_merged dataset has a total average nearest neighbor distance of: ", kd_avg_nn) + +#illustrate the use of the mean nearest neighbor on a mark: +#kd_avg_nn_mark = point_pattern.kDTree_nearest_neighbor(['animal-bites']) +#print("The new_haven_merged dataset with the mark 'animal-bites' mark has a total average nearest neighbor distance of: ", kd_avg_nn_mark) + +#kd_avg_nn_mark2 = point_pattern.kDTree_nearest_neighbor(['animal-bites','assault-wdangerous-weapon']) +#print("The new_haven_merged dataset with the mark 'animal-bites' and 'assault-wdangerous-weapon' marks has a total average nearest neighbor distance of", kd_avg_nn_mark2) + + +#ssssssillustrate the use of the g function: +#np_compute_g, ds1 = point_pattern.numpy_compute_g(12) +#print("The new_haven_merged dataset's g function results are:") +#for g in np_compute_g: +# print(g) + +np_compute_g_mark, ds2 = point_pattern.numpy_compute_g(12,['animal-bites']) +print("The new_haven_merged dataset's g function results with a mark of 'animal-bites' are: ") +for g in np_compute_g_mark: + print(g) + +np_compute_g_mark2, ds3 = point_pattern.numpy_compute_g(12,['animal-bites','assault-wdangerous-weapon']) +print("The new_haven_merged dataset's g function results with a marks of 'animal-bites' and 'assault-wdangerous-weapon' are: ") +for g in np_compute_g_mark2: + print(g) + +criticalList = point_pattern.critical_points() + +print(criticalList[0]) +print(criticalList[1]) + diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/pointPattern_test.py b/src/tests/pointPattern_test.py new file mode 100644 index 0000000..21ab494 --- /dev/null +++ b/src/tests/pointPattern_test.py @@ -0,0 +1,72 @@ +import unittest + +from .. import pointPattern +from .. import point + + +class TestPointPattern(unittest.TestCase): + def setUp(self): + #initilize points into point pattern + self.pointPattern = pointPattern.PointPattern() #create list + #now add points into that list + self.pointPattern.add_point(point.Point(1,1,'lavender')) + self.pointPattern.add_point(point.Point(2,2,'orange')) + self.pointPattern.add_point(point.Point(2,4,'orange')) + self.pointPattern.add_point(point.Point(1,5,'ash')) + self.pointPattern.add_point(point.Point(3,3,'rose')) + self.pointPattern.add_point(point.Point(3,2,'rose')) + self.pointPattern.add_point(point.Point(4,4,'ash')) + self.pointPattern.add_point(point.Point(5,5,'violet')) + self.pointPattern.add_point(point.Point(1,1,'lavender')) + self.pointPattern.add_point(point.Point(1,1,'lavender')) + self.pointPattern.add_point(point.Point(1,1,'lavender')) + + def test_coin(self): + self.assertEqual(self.pointPattern.coin_count(),4) + + def test_markList(self): + check = True + list = self.pointPattern.mark_list() + for l in list: + if l not in ['lavender','orange','rose','ash','violet']: + check = False + self.assertTrue(check) + #self.assertEqual(self.pointPattern.mark_list(),['lavender','orange','rose','ash','violet']) + + def test_mark_subset(self): + m1 = self.pointPattern.mark_subset('lavender') + m2 = self.pointPattern.mark_subset('violet') + self.assertEqual(len(m1),4) + self.assertEqual(len(m2),1) + + def test_random_points(self): + #check with user passed n + l1 = self.pointPattern.create_n_random_points(5) + self.assertEqual(len(l1),5) + + l2 = self.pointPattern.create_n_random_points() + self.assertEqual(len(l2),11) + + def test_k_realizations(self): + self.assertEqual(len(self.pointPattern.create_k_patterns(10)),10) + + # def test_g_function(self): + # self.assertAlmostEqual(self.pointPattern.compute_g(10), 0.111, places=3) + # self.assertAlmostEqual(self.pointPattern.compute_g(50), 0.020, places=3) + # self.assertAlmostEqual(self.pointPattern.compute_g(100), 0.010, places=3) + +# def test_g_function_numpy(self): + # self.assertAlmostEqual(self.pointPattern.numpy_compute_g(10), 0.111, places=3) + # self.assertAlmostEqual(self.pointPattern.numpy_compute_g(50), 0.020, places=3) + # self.assertAlmostEqual(self.pointPattern.numpy_compute_g(100), 0.010, places=3) + + #Test the nearest neighbor distance results from the KD-Tree against your original implementation. + def test_kdtree_nearest_neighbor(self): + #returns the average nearest neighbor distance for the points in the pointPattern, computed once. + dist1 = self.pointPattern.average_nearest_neighbor() + dist2 = self.pointPattern.kDTree_nearest_neighbor() #both of these take self.points + self.assertEqual(dist1,dist2) + + #now check that the numpy average nearest neighbor is working properly too: + dist3 = self.pointPattern.numpy_nearest_neighbor()#also uses self.points + self.assertEqual(dist1,dist3) \ No newline at end of file diff --git a/src/tests/point_test.py b/src/tests/point_test.py new file mode 100644 index 0000000..f338158 --- /dev/null +++ b/src/tests/point_test.py @@ -0,0 +1,71 @@ +import unittest +import random + + +class TestPoint(unittest.TestCase): + def setUp(self): + pass + + def test_xyCheck(self): + points = point.Point(5,4) + self.assertEqual(5,points.x) + self.assertEqual(4,points.y) + + def test_coincident(self): + point1 = point.Point(1,2) + + self.assertTrue(point1.patched_coincident((1,2))) + self.assertFalse(point1.patched_coincident((3,4))) + + def test_shift(self): + point1 = point.Point(1,0) + point1.patched_shift(1,2) + self.assertEqual((2,2),(point1.x,point1.y)) + + def test_marks(self): + random.seed(12345) + + marks = ['lavender','orange','rose','ash','violet','magenta','cerulean'] + + #list of points: + points = [] + markArray = [] + #instantiate 15 random points + for i in range(15): + new_point = point.Point(random.randint(0,9),random.randint(0,9),random.choice(marks)) + print(new_point.x) + print(new_point.y) + print(new_point.mark) + points.append(new_point) + + #count the amount of times a mark appears in the list + for p in points: + markArray.append(p.mark) + c = Counter(markArray) + + self.assertEqual(c["lavender"],4) + self.assertEqual(c["cerulean"],3) + self.assertEqual(c["violet"],3) + self.assertEqual(c["magenta"],2) + self.assertEqual(c["orange"],2) + self.assertEqual(c["ash"],1) + + def test_magic(self): + #Check the __add__ + point1 = point.Point(1,2) + point2 = point.Point(5,5) + self.assertEqual(point1 + point2, point.Point(6,7)) + + #check the __eq__ + point3 = point.Point(1,2) + print(point3 == point1) + self.assertTrue(point1 == point3) + + #check the __neq__ + print(point1 != point2) + self.assertTrue(point1 != point2) + + +from .. import point +from collections import Counter + diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..dc7d576 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,166 @@ + + +def n_random_Points(n,marks=[]): + + PointList = [point.Point(random.uniform(0,1),random.uniform(0,1),random.choice(marks)) for x in range(n)] + + return PointList + + +def manhattan_distance(a, b): + """ + Compute the Manhattan distance between two points + + Parameters + ---------- + a : tuple + A point in the form (x,y) + + b : tuple + A point in the form (x,y) + + Returns + ------- + distance : float + The Manhattan distance between the two points + """ + distance = abs(a[0] - b[0]) + abs(a[1] - b[1]) + return distance + + +def euclidean_distance(a, b): + """ + Compute the Euclidean distance between two points + + Parameters + ---------- + a : tuple + A point in the form (x,y) + + b : tuple + A point in the form (x,y) + + Returns + ------- + + distance : float + The Euclidean distance between the two points + """ + distance = math.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2) + return distance + + +def shift_point(points, x_shift, y_shift): + """ + Shift a point by some amount in the x and y directions + + Parameters + ---------- + point : tuple + in the form (x,y) + + x_shift : int or float + distance to shift in the x direction + + y_shift : int or float + distance to shift in the y direction + + Returns + ------- + new_x : int or float + shited x coordinate + + new_y : int or float + shifted y coordinate + + Note that the new_x new_y elements are returned as a tuple + + Example + ------- + >>> points = (0,0) + >>> shift_point(points, 1, 2) + (1,2) + """ + x = getx(points) + y = gety(points) + + x += x_shift + y += y_shift + + return x, y + + +def check_coincident(a, b): + """ + Check whether two points are coincident + Parameters + ---------- + a : tuple + A point in the form (x,y) + + b : tuple + A point in the form (x,y) + + Returns + ------- + equal : bool + Whether the points are equal + """ + return a == b + + +def check_in(points, point_list): + """ + Check whether point is in the point list + + Parameters + ---------- + point : tuple + In the form (x,y) + + point_list : list + in the form [point, point_1, point_2, ..., point_n] + """ + return points in point_list + + +def getx(points): + """ + A simple method to return the x coordinate of + an tuple in the form(x,y). We will look at + sequences in a coming lesson. + + Parameters + ---------- + point : tuple + in the form (x,y) + + Returns + ------- + : int or float + x coordinate + """ + return points[0] + + +def gety(points): + """ + A simple method to return the x coordinate of + an tuple in the form(x,y). We will look at + sequences in a coming lesson. + + Parameters + ---------- + point : tuple + in the form (x,y) + + Returns + ------- + : int or float + y coordinate + """ + return points[1] + +from . import point +import math +import random \ No newline at end of file diff --git a/view.py b/view.py new file mode 100644 index 0000000..30bb0d5 --- /dev/null +++ b/view.py @@ -0,0 +1,71 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +ZetCode PyQt4 tutorial + +This program creates a skeleton of +a classic GUI application with a menubar, +toolbar, statusbar and a central widget. + +author: Jan Bodnar +website: zetcode.com +last edited: September 2011 +""" + +import sys +from PyQt4 import QtGui + + +class View(QtGui.QMainWindow): + + def __init__(self): #calling the parent constructor + super(View, self).__init__() + + self.initUI() + + + def initUI(self): + + self.resize(350,250) + self.center() + textEdit = QtGui.QTextEdit() #adding a QTextEdit as the central widget + self.setCentralWidget(textEdit) + + #creates a toolbar to exit, along with adding a keyboard shortcut + exitAction = QtGui.QAction('Exit', self) + exitAction.setShortcut('Ctrl+Q') + exitAction.setStatusTip('Exit application') + exitAction.triggered.connect(self.close) + + #added a ready message to the status bar + self.statusBar().showMessage('Ready') + + #creating the file menu, and adding exit as an option + menubar = self.menuBar() + fileMenu = menubar.addMenu('&File') + fileMenu.addAction(exitAction) + + #creating a toolbar with an exit function + toolbar = self.addToolBar('Exit') + toolbar.addAction(exitAction) + + #setting the window name + self.setWindowTitle('Assignment 9') + self.show() + + #making it appear in the center of the screen + def center(self): + qr = self.frameGeometry() + cp = QtGui.QDesktopWidget().availableGeometry().center() + qr.moveCenter(cp) + self.move(qr.topLeft()) +def main(): + + app = QtGui.QApplication(sys.argv) + ex = View() + sys.exit(app.exec_()) + + +if __name__ == '__main__': + main() \ No newline at end of file