patternpythonMinor
improve the design of class “accuracy” in Python
Viewed 0 times
thedesignimprovepythonclassaccuracy
Problem
I am learning about the class and methods in Python.
The class Accuracy is a class of several (13 in total) statistic values between a reference polygon and one or more segmented polygons based on shapely module.
where
my class design (really basic and probably to improve) is:
```
class Accuracy(object):
def __init__ (self,ref,seg = None, noData = -9999):
if seg == None:
self.area = ref.area
self.perimeter = ref.length
self.centroidX = ref.centroid.x
self.centroidY = ref.centroid.y
self.data = [self.centroidX,
self.centroidY,
self.area,
self.perimeter,
noData,
noData,
noData,
noData,
noData]
else:
self.area = ref.area
self.perimeter = ref.length
self.centroidX = ref.centroid.x
self.centroidY = ref.centroid.y
self.segments = len(seg)
self.RAor = ra_or(ref,seg)
self.RAos = ra_os(ref,seg)
self.SimSize = sim_size(ref,seg)
self.AFI = AFI(ref,seg)
self.data = [self.centroidX,
self.centroidY,
self.area,
self.perimeter,
self.segments,
self.RAor,
The class Accuracy is a class of several (13 in total) statistic values between a reference polygon and one or more segmented polygons based on shapely module.
from numpy import average
#some stat
def ra_or(ref, seg):
return average([(ref.intersection(s).area/ref.area) for s in seg])
def ra_os(ref, seg):
return average([(ref.intersection(s).area/s.area) for s in seg])
def sim_size(ref, seg):
return average([(min(ref.area, s.area)/max(ref.area,s.area)) for s in seg])
def AFI(ref,seg):
return (ref.area - max([s.area for s in seg]))/ref.areawhere
ref.intersection(s).area is the area of intersection between reference and segmented polygon-imy class design (really basic and probably to improve) is:
```
class Accuracy(object):
def __init__ (self,ref,seg = None, noData = -9999):
if seg == None:
self.area = ref.area
self.perimeter = ref.length
self.centroidX = ref.centroid.x
self.centroidY = ref.centroid.y
self.data = [self.centroidX,
self.centroidY,
self.area,
self.perimeter,
noData,
noData,
noData,
noData,
noData]
else:
self.area = ref.area
self.perimeter = ref.length
self.centroidX = ref.centroid.x
self.centroidY = ref.centroid.y
self.segments = len(seg)
self.RAor = ra_or(ref,seg)
self.RAos = ra_os(ref,seg)
self.SimSize = sim_size(ref,seg)
self.AFI = AFI(ref,seg)
self.data = [self.centroidX,
self.centroidY,
self.area,
self.perimeter,
self.segments,
self.RAor,
Solution
If you plan on calling the four functions
Classes can help organize complex code, but they generally do not make your code faster. Do not use a class unless there is a clear advantage to be had -- through inheritance, or polymorphism, etc.
If you want faster code which uses less memory, avoid using a class here. Just define functions for each attribute.
If you want "luxurious" syntax -- the ability to reference each statistic via an attribute, then a class is fine.
If you plan on instantiating instances of
Note that when you write
To be clear, the advantage to using properties is that each instance of
Moreover, you can cache the result using Denis Otkidach's CachedAttribute decorator. Then the attribute is only computed once, and simply looked up every time thereafter.
Don't use an arbitrary value for
Here is how you could save your data (as a numpy array) to a CSV file:
And here is how you could read it back:
ra_or, ra_os, sim_size and AFI outside of Accuracy then it is good to keep them as functions. If they never get called except through Accuracy, then they should be made methods.Classes can help organize complex code, but they generally do not make your code faster. Do not use a class unless there is a clear advantage to be had -- through inheritance, or polymorphism, etc.
If you want faster code which uses less memory, avoid using a class here. Just define functions for each attribute.
If you want "luxurious" syntax -- the ability to reference each statistic via an attribute, then a class is fine.
If you plan on instantiating instances of
Accuracy but not always accessing all the attributes, you don't need to compute them all in __init__. You can delay their computation by using properties.@property
def area(self):
return self.ref.areaNote that when you write
accp1.area, the area method above will be called. Notice there are no parentheses after accp1.area. To be clear, the advantage to using properties is that each instance of
Accuracy will not compute all its statistical attributes until they are needed. The downside of using a property is that they are recomputed everytime the attribute is accessed. That may not be a downside if self.ref or self.seg ever change.Moreover, you can cache the result using Denis Otkidach's CachedAttribute decorator. Then the attribute is only computed once, and simply looked up every time thereafter.
Don't use an arbitrary value for
noData like noData = -9999. Use noData = np.nan, or simply skip noData and use np.nan directly. import numpy as np
from shapely.geometry import Polygon
nan = np.nan
class Accuracy(object):
def __init__(self, ref, seg=None):
self.ref = ref
self.seg = seg
@property
def area(self):
return self.ref.area
@property
def perimeter(self):
return self.ref.length
@property
def centroidX(self):
return self.ref.centroid.x
@property
def centroidY(self):
return self.ref.centroid.y
@property
def data(self):
return [self.centroidX,
self.centroidY,
self.area,
self.perimeter,
self.segments,
self.RAor,
self.RAos,
self.SimSize,
self.AFI]
@property
def segments(self):
if self.seg:
return len(self.seg)
else:
return nan
@property
def RAor(self):
if self.seg:
return np.average(
[(self.ref.intersection(s).area / self.ref.area) for s in self.seg])
else:
return nan
@property
def RAos(self):
if self.seg:
return np.average(
[(self.ref.intersection(s).area / s.area) for s in self.seg])
else:
return nan
@property
def SimSize(self):
if self.seg:
return np.average(
[(min(self.ref.area, s.area) / max(self.ref.area, s.area))
for s in self.seg])
else:
return nan
@property
def AFI(self):
if self.seg:
return (self.ref.area - max([s.area for s in self.seg])) / self.ref.area
else:
return nan
p1 = Polygon([(2, 4), (4, 4), (4, 2), (2, 2), (2, 4)])
p2 = Polygon([(0, 3), (3, 3), (3, 0), (0, 0), (0, 3)])
accp1 = Accuracy(p1, [p2])
print(accp1.data)
# [3.0, 3.0, 4.0, 8.0, 1, 0.25, 0.1111111111111111, 0.44444444444444442, -1.25]
accp1 = Accuracy(p1)
print(accp1.data)
# [3.0, 3.0, 4.0, 8.0, nan, nan, nan, nan, nan]Here is how you could save your data (as a numpy array) to a CSV file:
np.savetxt('/tmp/mytest.txt', np.atleast_2d(accp1.data), delimiter=',')And here is how you could read it back:
data = np.genfromtxt('/tmp/mytest.txt', dtype=None)
print(data)
# [ 3. 3. 4. 8. nan nan nan nan nan]Code Snippets
@property
def area(self):
return self.ref.areaimport numpy as np
from shapely.geometry import Polygon
nan = np.nan
class Accuracy(object):
def __init__(self, ref, seg=None):
self.ref = ref
self.seg = seg
@property
def area(self):
return self.ref.area
@property
def perimeter(self):
return self.ref.length
@property
def centroidX(self):
return self.ref.centroid.x
@property
def centroidY(self):
return self.ref.centroid.y
@property
def data(self):
return [self.centroidX,
self.centroidY,
self.area,
self.perimeter,
self.segments,
self.RAor,
self.RAos,
self.SimSize,
self.AFI]
@property
def segments(self):
if self.seg:
return len(self.seg)
else:
return nan
@property
def RAor(self):
if self.seg:
return np.average(
[(self.ref.intersection(s).area / self.ref.area) for s in self.seg])
else:
return nan
@property
def RAos(self):
if self.seg:
return np.average(
[(self.ref.intersection(s).area / s.area) for s in self.seg])
else:
return nan
@property
def SimSize(self):
if self.seg:
return np.average(
[(min(self.ref.area, s.area) / max(self.ref.area, s.area))
for s in self.seg])
else:
return nan
@property
def AFI(self):
if self.seg:
return (self.ref.area - max([s.area for s in self.seg])) / self.ref.area
else:
return nan
p1 = Polygon([(2, 4), (4, 4), (4, 2), (2, 2), (2, 4)])
p2 = Polygon([(0, 3), (3, 3), (3, 0), (0, 0), (0, 3)])
accp1 = Accuracy(p1, [p2])
print(accp1.data)
# [3.0, 3.0, 4.0, 8.0, 1, 0.25, 0.1111111111111111, 0.44444444444444442, -1.25]
accp1 = Accuracy(p1)
print(accp1.data)
# [3.0, 3.0, 4.0, 8.0, nan, nan, nan, nan, nan]np.savetxt('/tmp/mytest.txt', np.atleast_2d(accp1.data), delimiter=',')data = np.genfromtxt('/tmp/mytest.txt', dtype=None)
print(data)
# [ 3. 3. 4. 8. nan nan nan nan nan]Context
StackExchange Code Review Q#23929, answer score: 2
Revisions (0)
No revisions yet.