HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

improve the design of class “accuracy” in Python

Submitted by: @import:stackexchange-codereview··
0
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.

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.area


where ref.intersection(s).area is the area of intersection between reference and segmented polygon-i

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,

Solution

If you plan on calling the four functions 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.area


Note 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.area
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]
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.