Python paresseux évaluation numpy ndarray

J'ai un grand tableau 2D que je voudrais déclarer une seule fois, et change occasionnellement seulement certaines valeurs en fonction d'un paramètre, sans traverser le tableau entier.

Pour construire ce tableau, j'ai sous-classé la classe ndarray numpy avec dtype = object et assigner aux éléments que je veux changer une fonction par exemple. :

def f(parameter):
     return parameter**2

for i in range(np.shape(A)[0]):
    A[i,i]=f
    for j in range(np.shape(A)[0]):
        A[i,j]=1.

J'ai alors surchargé la méthode __ getitem __ pour qu'elle renvoie l'évaluation de la fonction avec paramètre donné si elle est appelable, sinon retourne la valeur elle-même.

    def __getitem__(self, key):
        value = super(numpy.ndarray, self).__getitem__(key)
        if callable(value):
            return value(*self.args)
        else:
            return value

self.args a été précédemment donné à l'instance de myclass.

Cependant, j'ai besoin de travailler avec des tableaux float à la fin, et je ne peux pas simplement convertir ce tableau en un tableau dtype = float avec cette technique. J'ai également essayé d'utiliser des vues numériques, ce qui ne fonctionne pas non plus pour dtype = object .

Avez-vous une meilleure alternative? Dois-je remplacer la méthode de vue plutôt que getitem?

Edit I will maybe have to use Cython in the future, so if you have a solution involving e.g. C pointers, I am interested.

0
@BasSwinckels: Je peux avoir beaucoup de f avec de nombreux arguments. La raison pour laquelle j'ai choisi cette approche est précisément de faciliter l'écriture de telles matrices, où je peux potentiellement avoir plus de variables.
ajouté l'auteur Damlatien, source
@ hpaulj: dok est très intéressant. Cependant, je ne peux pas l'utiliser avec dtype = object, comme dans l'exemple que j'ai montré ci-dessus: github.com/scipy/scipy/issues/2528
ajouté l'auteur Damlatien, source
@rth: La raison pour laquelle j'ai besoin d'une évaluation paresseuse plutôt que d'accéder au tableau avec la clé (même efficacement), c'est que chaque affectation peut être liée à différents types d'indices. Pour l'exemple ci-dessus, je ne définis que la diagonale à être variable. J'aurais pu par exemple aussi affecter une ligne (ou plus compliquée) à une autre fonction g.
ajouté l'auteur Damlatien, source
C'est une approche intéressante, mais je ne suis pas sûr que les tableaux numériques soient adaptés. En général, lorsque vous travaillez avec numpy, vous devez utiliser des opérations vectorisées en utilisant des tableaux ou des tranches entiers, et non un accès élément par élément. Sous-classement ndarrays comme vous le faites, vous perdez essentiellement tout avantage des opérations numpy rapides. Vous feriez peut-être mieux de créer votre propre classe à partir de zéro et de tout sauvegarder, dans des structures python pures (listes, etc.). Performance sage, ça va être comparable. Pourquoi
ajouté l'auteur rth, source
Connaissez-vous scipy.sparse ? Le format dok est un dictionnaire, avec le tuple (i, j) comme clés. Cela et lil (liste de listes) sont les 2 moyens les plus rapides d'accéder/modifier les éléments sélectionnés.
ajouté l'auteur hpaulj, source
Je ne pensais pas utiliser dok directement, mais plutôt l'utiliser comme modèle pour votre propre sous-classe de dictionnaire. Mieux encore en faire une sous-classe de dictionnaire par défaut, avec f (key) comme valeur par défaut.
ajouté l'auteur hpaulj, source
Avez-vous seulement une seule fonction f ? Avec des arguments constants?
ajouté l'auteur Bas Swinckels, source

1 Réponses

Dans ce cas, il n'a pas de sens de lier une fonction de transformation, à chaque index de votre tableau.

Au lieu de cela, une approche plus efficace consisterait à définir une transformation, en tant que fonction, avec un sous-ensemble du tableau auquel elle s'applique. Voici une implémentation de base,

import numpy as np

class LazyEvaluation(object):
    def __init__(self):
        self.transforms = []

    def add_transform(self, function, selection=slice(None), args={}):
        self.transforms.append( (function, selection, args))

    def __call__(self, x):
        y = x.copy() 
        for function, selection, args in self.transforms:
            y[selection] = function(y[selection], **args)
        return y

cela peut être utilisé comme suit:

x = np.ones((6, 6))*2

le = LazyEvaluation()
le.add_transform(lambda x: 0, [[3], [0]]) # equivalent to x[3,0]
le.add_transform(lambda x: x**2, (slice(4), slice(4,6)))  # equivalent to x[4,4:6]
le.add_transform(lambda x: -1,  np.diag_indices(x.shape[0], x.ndim), ) # setting the diagonal 
result =  le(x)
print(result)

qui imprime,

array([[-1.,  2.,  2.,  2.,  4.,  4.],
       [ 2., -1.,  2.,  2.,  4.,  4.],
       [ 2.,  2., -1.,  2.,  4.,  4.],
       [ 0.,  2.,  2., -1.,  4.,  4.],
       [ 2.,  2.,  2.,  2., -1.,  2.],
       [ 2.,  2.,  2.,  2.,  2., -1.]])

De cette façon, vous pouvez facilement prendre en charge toutes les indexations Numpy avancées (accès élément par élément, découpage, indexage fantaisie, etc.) tout en conservant vos données dans un tableau avec un type de données natif ( float , int , etc) ce qui est beaucoup plus efficace que d'utiliser dtype = 'object' .

0
ajouté
Merci, j'ai implémenté cela essentiellement comme une sous-classe de dict, et cela fonctionne à peu près comme je le veux. Cependant, juste comme une curiosité, je voudrais savoir s'il est possible de réaliser quelque chose de similaire en C/C ++ avec des pointeurs d'une manière beaucoup plus élégante? Par exemple, on pourrait déclarer une table de pointeurs (float), et au moment de la déclaration chaque pointeur dirigerait soit vers zéro, soit le résultat d'une fonction, de sorte que je puisse mettre à jour la matrice en appelant une (ou plusieurs) fonction.
ajouté l'auteur Damlatien, source
Mais pour ce faire, atteindre l'appel d'une fonction devrait être lié à un pointeur particulier, et je ne suis pas sûr que ce soit faisable. J'espère que j'étais assez clair.
ajouté l'auteur Damlatien, source