Comment accéder aux lignes adjacentes avec le curseur?

8

Dans la capture d'écran ci-jointe, les attributs contiennent deux champs d'intérêt "a" et "b". Je veux écrire un script pour accéder aux lignes adjacentes afin de faire des calculs. Pour accéder à une seule ligne, j'utiliserais le UpdateCursor suivant:

fc = r'C:\path\to\fc'

with arcpy.da.UpdateCursor(fc, ["a", "b"]) as cursor:
     for row in cursor:
          # Do something

Par exemple, avec OBJECTID 4, je suis intéressé à calculer la somme des valeurs de ligne dans le champ "a" adjacent à la ligne OBJECTID 4 (c'est-à-dire 1 + 3) et à ajouter cette valeur à la ligne OBJECTID 4 dans le champ "b". Comment puis-je accéder aux lignes adjacentes avec le curseur pour effectuer ce genre de calculs?

entrez la description de l'image ici

Aaron
la source

Réponses:

7

Si c'était moi, je passerais par le tableau une fois en créant un dictionnaire où la clé est OBJECTID et l' élément est la valeur dans le champ "a". Ensuite, je parcourais la table avec un curseur de mise à jour pour obtenir l’OBJECTID et à partir de là, je pouvais obtenir les valeurs adjacentes du dictionnaire, les additionner et les réécrire dans le champ "b".

Mais dans votre capture d'écran, vous avez des cas spéciaux pour les lignes 3 et 8 car ils n'ont qu'une seule ligne adjacente. Que comptez-vous en faire?

Hornbydd
la source
+1 C'est une bonne solution car elle peut être utilisée d'une manière qui respecte le modèle de données relationnel. Dans ce modèle, la séquence dans laquelle les lignes sont traitées n'est pas définie, donc toute procédure qui repose sur l'hypothèse qu'elles seront itérées dans le même ordre affiché dans une vue de table doit être considérée comme non fiable. En utilisant n'importe quelle clé - pas seulement - OBJECTIDcette solution peut identifier de manière fiable les voisins en fonction des valeurs de cette clé. Cependant, les dictionnaires ne prennent généralement pas en charge une recherche "suivante" ou "précédente". Vous avez besoin de quelque chose comme un Trie .
whuber
4

Lors du bouclage sur les lignes, vous devez garder une trace des valeurs précédentes. C'est une façon de procéder:

 previous_value = None
 cursor = arcpy.UpdateCursor(fc, fields, sort_fields)
 for i, in_row in enumerate(cursor):
      current_value = in_row.getValue("ColName")
      if not previous_value: 
           previous_value = current_value 
           continue
       #
       # here you have access to the current and previous value
       #

       previous_value = current_value

ou, si la table n'est pas énorme, je construirais probablement un dictionnaire, comme d = {a: b} puis dans le curseur de mise à jour, accédez aux données du dictionnaire: d.get (a + 1) ou d.get (a -1) pour faire le calcul ..

Matej
la source
3

J'ai accepté la réponse de @Hornbydd pour m'avoir conduit vers une solution de dictionnaire. Le script joint effectue les actions suivantes:

  1. Parcourez un FC et remplissez un dictionnaire avec key = OID et value = "MyField" à l'aide d'un SearchCursor.
  2. Démarrer un UpdateCursor
  3. Créer une logique pour gérer les première et dernière lignes (c'est-à-dire lorsqu'il n'y a pas de ligne précédente ou consécutive)
  4. Lier la ligne UpdateCursor à l'OID du dictionnaire et à la valeur du champ
  5. Faites le traitement ...

import arcpy, collections

fc = r'C:\path\to\fc'

# Populate a dictionary with key = OID and value = "a"
names = collections.defaultdict(list)

for name1, name2 in arcpy.da.SearchCursor(fc, ("OBJECTID", "a")):
    names[name1].append(name2)

# Use .values() class to access adjacent rows

with arcpy.da.UpdateCursor(fc, ["OBJECTID", "a", "b"]) as cursor:
    for row in cursor:
        # Get first and last rows for special processing
        if row[0] == names.keys()[0] or row[0] == names.keys()[-1]:
            """Note that the first and last row will need special rules because
             they cannot reference either the previous or next row.  In this case
             the features are skipped"""
            pass

        else:
            """This needs to be corrected because OID = (row[0] - 1)"""
            # Now link the dictionary names with row[0] (i.e. OBJECTID)
            x = names.values()[row[0] - 2]  # e.g. OID 2 (pre)
            y = names.values()[row[0] - 1]  # e.g. OID 3 (current row)
            z = names.values()[row[0]]      # e.g. OID 4 (post)

            # Now do the calculation
            row[2] = x + z
            cursor.updateRow(row)
Aaron
la source
Je ne comprends pas comment: si la ligne [0] == names.keys () [0] ou la ligne [0] == names.keys () [- 1]: fonctionnerait? Les valeurs dans le defaultdict ne sont pas ordonnées, n'est-ce pas?
ianbroad
2

Le module d'accès aux données est assez rapide et vous pouvez créer un SearchCursorpour enregistrer toutes les valeurs de «a» dans une liste, puis créer un UpdateCursorpour parcourir chaque ligne et sélectionner dans la liste pour mettre à jour les lignes «b» nécessaires. De cette façon, vous n'avez pas à vous soucier de l'enregistrement des données entre les lignes =)

Donc quelque chose comme ça:

fc = r'C:\path\to\fc'
fields = ["a","b"]
aList = []
index = 0

with arcpy.da.SearchCursor(fc,fields) as cursor:
     for row in cursor:
         aList.append(row[0])    # This saves each value of 'a'
with arcpy.da.UpdateCursor(fc,fields) as cursor:
     for row in cursor:
         if index-1 > 0 AND index+1 <= len(aList):     # Keeps 'b' null if out of list range 
                                                       # or at end of list
             row[1] = aList[index-1]+aList[index+1]
         index += 1

C'est une solution assez grossière mais je l'ai utilisée récemment pour contourner un problème très similaire. Si le code ne fonctionne pas, espérons-le, il vous met sur la bonne voie!

Edit: dernière modification de l'instruction if de AND en OR Edit2: modification en arrière. Ahh la pression de mon premier post StackExchange!

romain
la source
Au lieu de modifier ma réponse une autre fois, je donnerai mes 0,02 $ pour expliquer pourquoi je pense que c'est une bonne solution au problème. Itérer sur les lignes facilite également l'itération des listes avec index + = 1. Bien que je n'aie pas beaucoup réfléchi à la mécanique du code, il devrait transmettre le point que je voulais vous faire comprendre. Bonne chance!
Roman
1
Quelques remarques: 1) Il peut être plus rapide pour les grands ensembles de données si vous utilisez une compréhension de liste à remplir aListau lieu d'ajouter chaque entrée. 2) Utilisez enumerate()au lieu d'avoir un compteur séparé pour l'index.
Paul
-1

Vous avez d'abord besoin d'un curseur de recherche; Je ne pense pas que vous puissiez obtenir des valeurs avec un curseur de mise à jour. Ensuite, à chaque itération, utilisez aNext = row.next (). GetValue ('a') pour obtenir la valeur de la ligne suivante.

Pour obtenir la valeur de la ligne précédente, je définirais une variable à l'extérieur de la boucle for égale à zéro. Ceci est mis à jour pour égaler la valeur actuelle des lignes de 'a'. Vous pouvez ensuite accéder à cette variable dans l'itération suivante.

Cela satisferait alors votre équation de B = A (rowid-1) + A (rowid + 1)

user25074
la source
Vous n'avez pas besoin de basculer vers un curseur de recherche pour utiliser getValue. C'est une méthode de l'objet ROW, pas de l'objet CURSOR.
msayler
En fait, ils utilisent des curseurs DA, donc je ne pense pas que getValue s'applique même. Je pense que les curseurs DA utilisent des index à la place.
msayler
1
Les curseurs de mise à jour sont capables de lire et d'écrire des données. (Utile, par exemple, pour lire une valeur fieldAet l'utiliser pour calculer une nouvelle valeur pour fieldB.)
Erica