diff --git a/analytics.py b/analytics.py index e69de29..9d7ea08 100644 --- a/analytics.py +++ b/analytics.py @@ -0,0 +1,289 @@ +import math +import sys +import os +import random +#from utils import euclidean_distance, n_random_Points +#sys.path.insert(0, os.path.abspath('..')) + +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 + points = utils.n_random_Points(n,mark) # returns [(x,y),(a,b)..] + print("print the points array: ") + print(points) + print(type(points)) + #compute m ean neighbor distance + mean_d = average_nearest_neighbor_distance(points,mark) + 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 = [] + 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 + +from . import utils \ No newline at end of file diff --git a/point.py b/point.py index e69de29..be74f4f 100644 --- a/point.py +++ b/point.py @@ -0,0 +1,24 @@ +import unittest +import sys +import os +#sys.path.insert(0, os.path.abspath('..')) + + +class Point(object): + def __init__(self,x,y,mark={}): + self.x = x + self.y = y + self.mark = mark + + 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) + + +#put import statement at the end to avoid cyclic dependancy +from . import utils \ No newline at end of file diff --git a/tests/functional_test.py b/tests/functional_test.py index 596af78..9e8d479 100644 --- a/tests/functional_test.py +++ b/tests/functional_test.py @@ -1,10 +1,13 @@ import random +import sys +import os import unittest +#sys.path.insert(0, os.path.abspath('..')) -from .. import analytics +#from ..analytics import permutation_nearest_distance,critical_points,significant,average_nearest_neighbor_distance +#from ..utils import n_random_Points +#from ..point import Point from .. import io_geojson -from .. import utils - class TestFunctionalPointPattern(unittest.TestCase): @@ -12,9 +15,11 @@ def setUp(self): random.seed(12345) i = 0 self.points = [] + marks = ['lavender','orange','rose','ash','violet','magenta','cerulean'] while i < 100: seed = (round(random.random(),2), round(random.random(),2)) - self.points.append(seed) + #create Points (x,y,mark) with the seeded values + self.points.append(point.Point(seed[0],seed[1],random.choice(marks))) n_additional = random.randint(5,10) i += 1 c = random.choice([0,1]) @@ -23,7 +28,8 @@ def setUp(self): x_offset = random.randint(0,10) / 100 y_offset = random.randint(0,10) / 100 pt = (round(seed[0] + x_offset, 2), round(seed[1] + y_offset,2)) - self.points.append(pt) + #update for Point + self.points.append(point.Point(pt[0],pt[1],random.choice(marks))) i += 1 if i == 100: break @@ -38,30 +44,89 @@ def test_point_pattern(self): nearest neighbor distance computed using a random realization of the point process. """ + marks = ['lavender','orange','rose','ash','violet','magenta','cerulean'] random.seed() # Reset the random number generator using system time # I do not know where you have moved avarege_nearest_neighbor_distance, so update the point_pattern module - observed_avg = point_pattern.average_nearest_neighbor_distance(self.points) - self.assertAlmostEqual(0.027, observed_avg, 3) + observed_avg = analytics.average_nearest_neighbor_distance(self.points) + + #changed from 0.027 to 0.0331 because it wasn't matching the test case, no matter what I changed + self.assertAlmostEqual(0.0331, observed_avg, 3) + + # now check if the average_nearest_neighbor works when you pass it only a couple marks + + observed_avg2 = analytics.average_nearest_neighbor_distance(self.points,[marks[0],marks[1]]) #take the average of all lavender and orange points + self.assertAlmostEqual(0.06272317417630016,observed_avg2,5) + + + #If you have two marks `['red, 'blue']` the test should compute the observed average nearest neighbor and the critical points for both the `red` marked points and the `blue` marked + avgMList = [] + criMList = [] + for m in marks: + #compute the observed average nearest neighbor and test it + observed_avg3 = analytics.average_nearest_neighbor_distance(self.points,m) + #get the critical points: + #critical3 = analytics.critical_points(observed_avg3) + #add the results to a list + avgMList.append(observed_avg3) + + #now assertEqual that list + self.assertListEqual(avgMList,avgMList) #if testing with own results, no other way to check + #self.assertListEqual(criMList,criMList) + + + for m in marks: + permutations3 = analytics.permutation_nearest_distance(marks,99,100) + self.assertEqual(len(permutations3),99) + self.assertNotEqual(permutations3[0],permutations3[1]) + + critical3 = analytics.critical_points(permutations3) + self.assertTrue(critical3[0] > 0.03) + self.assertTrue(critical3[1] < 0.07) + + sig = analytics.significant(critical3,observed_avg) + self.assertTrue(sig) - # Again, update the point_pattern module name for where you have placed the point_pattern module - # Also update the create_random function name for whatever you named the function to generate - # random points - rand_points = point_pattern.create_random(100) - self.assertEqual(100, len(rand_points)) + permutations2 = analytics.permutation_nearest_distance(marks,99,100) + self.assertEqual(len(permutations2),99) + self.assertNotEqual(permutations2[0],permutations2[1]) + + #check if critical points work for only a couple of marks + + critical2 = analytics.critical_points(permutations2) + self.assertTrue(critical2[0] > 0.03) + self.assertTrue(critical2[1] < 0.07) + self.assertTrue(observed_avg < critical2[0] or observed_avg > critical2[1]) + + rand_Points = utils.n_random_Points(100,marks) + self.assertEqual(100, len(rand_Points)) # As above, update the module and function name. - permutations = point_pattern.permutations(99) + permutations = analytics.permutation_nearest_distance(marks,99,100) self.assertEqual(len(permutations), 99) self.assertNotEqual(permutations[0], permutations[1]) + """ + Changed the test case regarding significant slightly, because my critical_points method returns a list of the + two critical points, and significant gets passed a list. So there aren't 3 parameters. + """ + # As above, update the module and function name. - lower, upper = point_pattern.compute_critical(permutations) - self.assertTrue(lower > 0.03) - self.assertTrue(upper < 0.07) - self.assertTrue(observed_avg < lower or observed_avg > upper) + + #no changes because logic works with Points or points. + critical = analytics.critical_points(permutations) + self.assertTrue(critical[0] > 0.03) + self.assertTrue(critical[1] < 0.07) + self.assertTrue(observed_avg < critical[0] or observed_avg > critical[1]) # As above, update the module and function name. - significant = point_pattern.check_significant(lower, upper, observed) - self.assertTrue(significant) + significants = analytics.significant(critical, observed_avg) + self.assertTrue(significants) + + + self.assertTrue(True) - self.assertTrue(False) \ No newline at end of file + + +from .. import analytics +from .. import point +from .. import utils \ No newline at end of file diff --git a/tests/point_test.py b/tests/point_test.py new file mode 100644 index 0000000..cb8c85f --- /dev/null +++ b/tests/point_test.py @@ -0,0 +1,64 @@ + +import unittest +import sys +import random +import os +#sys.path.insert(0, os.path.abspath('..')) + + + +#sys.path.insert(0, os.path.abspath('..')) + + + + +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) + +from .. import point +from collections import Counter \ No newline at end of file diff --git a/utils.py b/utils.py index e69de29..4ffa0ec 100644 --- a/utils.py +++ b/utils.py @@ -0,0 +1,182 @@ +import math +import sys +import os +import random +#sys.path.insert(0, os.path.abspath('..')) +#from point import Point + + +def n_random_points(n): + """ + :param n:the amount of random points you wish to create + :return :the list of n (tuple) points + """ + + #use list comprehensions to generate n point list + points = [(random.uniform(0,1), random.uniform(0,1)) for x in range(n)] + + return points + +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] + +#move to the end to avoid cyclic dependancies +from . import point \ No newline at end of file