Atributes of class in numpy array - python

I have a class like:
class MyClass:
def __init__( self, params):
self.A = params[0]
self.B = params[1]
self.C = params[2]
and a numpy array built from instances of this class:
import numpy as np
ArrayA = np.empty((3,4),dtype = object)
for ii in range(3):
for jj in range(4):
ArrayA[ii,jj] = MyClass(np.random.rand(3))
I want to retrieve "MyClass.B" for ArrayA where "MyClass.A" is minimum, so I did:
WhereMin = np.where(ArrayA[:,:].A)
MinB = ArrayA[WhereMin].B
but that does not work. Any ideas?
EDIT:
When I run the above code I get the following error:
----> WhereMin = np.nanmin(ArrayA[:,:].A)
AttributeError: 'numpy.ndarray' object has no attribute 'A'
When I would expect to get an array of indices to use in "MinB".
Possible Solution
I found a possible solution to my problem:
Min = np.nanmin([[x.A for x in XX] for XX in ArrayA])
XXX = [[x for x in XX if x.A == Min] for XX in ArrayA]
MinB = [XX for XX in XXX if XX != [] ][0][0].B
Might not be too elegant, but does the job. Thank you all!

The .A attribute belongs to each individual element of ArrayA, not to the array as a whole. So, ArrayA[0,0].A is valid, because ArrayA[0,0] points to an instance of MyClass, but ArrayA[:,:] returns a copy of the original ndarray.
I would consider reorganizing your data so that you keep everything you want in the .A attribute in a single numpy array, and everything in .B in a single numpy array, etc. That would have two advantages, 1) you would be able to use where, and 2) your numpy arrays would be of dtype=float (you lose the advantage of numpy if you have to use dtype=object).

You can create a structured numpy array. Pass dtype a list of tuples of field name and data type. You can then access the complete array of a given field by indexing the array by the field name. To rework your example:
ArrayA = np.zeros((3,4),dtype=[('A','<f4'),('B','<f4'),('C','<f4')])
for ii in range(3):
for jj in range(4):
ArrayA[ii,jj] = np.random.rand(3)
minA = ArrayA['A'].min()
WhereMin = np.where(a['A'] == minA)
MinB = ArrayA[WhereMin]['B']

Related

Why does python change all instances of a class upon changing one single instance?

In the following python script I initialize attribute v of two instances of a class (tt1 and tt2) with the same array s. Then I apply the method "add" to only one of the instances, changing attribute v. However, v is changed not only for tt1, but also for tt2. Moreover, s is also changed. If s is defined as a scalar value instead of an array (and method add() changed accordingly), attribute v is changed only for tt1. Why is this? Many thanks for your feedback.
Here is the script:
import numpy as np
s = np.array([1,2,3])
class test_class:
def __init__(self,vv):
self.v = vv
def add(self,h,i):
aux = self.v
aux[i] += h
self.v = aux
tt1=test_class(s)
tt2=test_class(s)
tt1.add(1,0)
print('s = ', s)
print('tt1.v = ',tt1.v)
print('tt2.v = ',tt2.v)
This is the output:
s = [2 2 3]
tt1.v = [2 2 3]
tt2.v = [2 2 3]
You are just using the reference but the address to data structure is same list you have intialized at first try using deepcopy which creates another list and you can work independently.

Slice an array with a list of indices

Suppose a = array([1,2,3]) and b = array([4,5,6]). I want to slice a and b using a list and perform some operation on each part, then return the result to an array. For example i propose a dummy function that demonstrates the usage:
def dummy_function(i):
A = sum(a[:i])
B = sum(cumsum(b[i:]))
return A*B
For example, this function would return dummy_function(2) = 18, and dummy_function(1) = 16 but I would like to evaluate it using a list as its argument:
>>> dummy_function([2,1])
array([18,16])
Instead I get IndexError: invalid slice. I don't want to use a loop to iterate over the elements of [2,1] because I believe it can be done more effectively. How can I do what I want?
I don't know if I understood what you want correctly, but this worked for me:
import numpy as np
def func(i):
a = np.array([1,2,3])
b = np.array([4,5,6])
A = np.sum(a[:i])
B = np.cumsum(b[i:])
C = A*B
return C[0]
print(func(2))
The result is 18
If you want to give your 'func' a list as argument, then you probably should loop over the list elements..

In an instance of a class, can you use a value returned from a one method, as a parameter for calling another method

class A:
def __init__(self, matrix=[]):
self.matrix = matrix
def dimension(self):
return len(self.matrix), len(self.matrix[0])
def reduce_matrix(self, i, j):
temp = self.matrix[:i-1] + self.matrix[i:]
M = A([row[:j-1] + row[j:] for row in temp])
return M
def determinant(self):
(nrows, ncols) = self.dimension()
if nrows != ncols:
return ("Cannot find determinant of non-square matrix.")
elif nrows == 1:
return self.matrix[0][0]
else:
M = A(sum([ ((-1)**j) * self.matrix[0][j] * self.reduce_matrix(1,
j+1).determinant() for j in range(ncols) ]))
return M
Hello, I just started OOP so I have a question regarding using the returned value of a method in place of the "instance name" that is passed as "self" for some other method.
I included the rest of the code that just makes the calculations for a matrix problem, but I'm only focusing on the "self.reduce_matrix(1, j+1)).determinant()" bit of it. I need to take a matrix (which is a list of lists ex. [[1,2,3],[4,5,6]] ), and perform the "reduce_matrix" method 1 time per column in the matrix, and each time, the "determinant(self)" method passes in the value returned from the other method. When I run it, it says that "list object has no attribute "determinant"" because I can't pass in "self" to the determinant method like I usually do --> "self.determinant(self)"
Any suggestions will be very appreciated
In reduce_matrix:
create M as M = A([row[:j-1] + row[j:] for row in temp])
do not return M.matrix but M itself.
That said, numpy is there for all kind of matrix operations and more.

In NumPy, how do I set array b's data to reference the data of array a?

Say I have ndarray a and b of compatible type and shape. I now wish for the data of b to be referring to the data of a. That is, without changing the array b object itself or creating a new one. (Imagine that b is actually an object of a class derived from ndarray and I wish to set its data reference after construction.) In the following example, how do I perform the b.set_data_reference?
import numpy as np
a = np.array([1,2,3])
b = np.empty_like(a)
b.set_data_reference(a)
This would result in b[0] == 1, and setting operations in one array would affect the other array. E.g. if we set a[1] = 22 then we can inspect that b[1] == 22.
N.B.: In case I controlled the creation of array b, I am aware that I could have created it like
b = np.array(a, copy=True)
This is, however, not the case.
NumPy does not support this operation. If you controlled the creation of b, you might be able to create it in such a way that it uses a's data buffer, but after b is created, you can't swap its buffer out for a's.
Every variable in python is a pointer so you can use directly = as follow
import numpy as np
a = np.array([1,2,3])
b = a
You can check that b refers to a as follow
assert a[1] == b[1]
a[1] = 4
assert a[1] == b[1]
Usually when functions are not always supposed to create their own buffer they implement an interface like
def func(a, b, c, out=None):
if out is None:
out = numpy.array(x, y)
# ...
return out
that way the caller can control if an existing buffer is used or not.

concatenate numpy arrays that are class instance attributes in python

I am attempting to use a class that strings together several instances of another class as a numpy array of objects. I want to be able to concatenate attributes of the instances that are contained in the numpy array. I figured out a sloppy way to do it with a bunch of for loops, but I think there must be a more elegant, pythonic way of doing this. The following code does what I want, but I want to know if there is a cleaner way to do it:
import numpy as np
class MyClass(object):
def __init__(self):
self.a = 37.
self.arr = np.arange(5)
class MyClasses(object):
def __init__(self):
self.N = 5
# number of MyClass instances to become attributes of this
# class
def make_subclas_arrays(self):
self.my_class_inst = np.empty(shape=self.N, dtype="object")
for i in range(self.N):
self.my_class_inst[i] = MyClass()
def concatenate_attributes(self):
self.a = np.zeros(self.N)
self.arr = np.zeros(self.N * self.my_class_inst[0].arr.size)
for i in range(self.N):
self.a[i] = self.my_class_inst[i].a
slice_start = i * self.my_class_inst[i].arr.size
slice_end = (i + 1.) * self.my_class_inst[i].arr.size
self.arr[slice_start:slice_end] = (
self.my_class_inst[i].arr )
my_inst = MyClasses()
my_inst.make_subclas_arrays()
my_inst.concatenate_attributes()
Edit: Based on the response from HYRY, here is what the methods look like now:
def make_subclass_arrays(self):
self.my_class_inst = np.array([MyClass() for i in range(self.N)])
def concatenate_attributes(self):
self.a = np.hstack([i.a for i in self.my_class_inst])
self.arr = np.hstack([i.arr for i in self.my_class_inst])
you can use numpy.hstack() to concatenate arrays:
def concatenate_attributes(self):
self.a = np.hstack([o.a for o in self.my_class_inst])
self.arr = np.hstack([o.arr for o in self.my_class_inst])
See Also
vstack : Stack arrays in sequence vertically (row wise).
dstack : Stack arrays in sequence depth wise (along third axis).
concatenate : Join a sequence of arrays together.
For the latter function I would recommend this:
init = []
ContainerClass.arr = np.array([init + Array(myclass.arr) for myclass in self.my_class_inst])
typecast numpy array to normal array, catenate and typecast it back. Assuming now that you have simple 1D arrays. I don't remember by heart if numpy array has catenation function. You can use that instead of '+' sign and save the trouble of typecasting.
For the first you have the simplest form I can think of, although I usually use normal arrays instead of numpy ones for objects.
If you want to be really clever you can create an __add__ function for both of the classes. Then you can use '+' sign to add classes. a + b calls a.__add__(b). Now you would have to create functions that have following properties
MyClass + MyClass returns new MyClasses instance with a and b inside
MyClasses + MyClass adds MyClass to MyClasses in a way you want
Now if a,b,c,d are myClass instances, a+b+c+d should return MyClasses -class which contains MyClass instances a,b,c and d and their combined arrays. This would be the pythonic way, although its a bit too complicated in my taste.
edit:
Ok, sorry my bad. I did not have python when I wrote the code. This is the correct version:
init = []
my_inst.arr = np.array([init + list(myclass.arr.flat) for myclass in my_inst.my_class_inst]).flatten()
This is what I meant with the __add__ (and the pythonic way... regadless of its complicatedness):
import numpy as np
class MyClass(object):
def __init__(self):
self.a = 37.
self.arr = np.arange(5)
def __add__(self, classToAdd):
a = MyClasses() + self + classToAdd
return a
class MyClasses(object):
def __init__(self):
self.N = 0
self.my_class_inst = np.array([])
self.a = np.array([])
self.arr = np.array([])
def __add__(self, singleClass):
self.my_class_inst = np.hstack([self.my_class_inst, singleClass])
self.a = np.hstack([self.a, singleClass.a])
self.arr = np.hstack([self.arr, singleClass.arr])
self.N = self.my_class_inst.shape[0]
return self
#add_test = MyClass() + MyClass()
add_test = np.sum([MyClass() for i in range(5)])
print add_test.a, add_test.arr, add_test.N
print add_test.__class__, add_test.my_class_inst[0].__class__

Categories