Calculer des nombres séquentiels dans une table triée à l'aide d'ArcGIS Desktop?

11

Existe-t-il un moyen de calculer un champ trié avec des nombres séquentiels? J'ai vu Classe d'entités de tri pour calculer un champ d'ID séquentiel à l'aide d'ArcGIS Field Calculator? qui explique comment calculer les nombres séquentiels, mais cela est toujours calculé sur ordre FID, pas sur ordre trié.

#Pre-logic Script Code:
rec=0
def autoIncrement(): 
    global rec 
    pStart = 1  
    pInterval = 1 
    if (rec == 0):  
        rec = pStart  
    else:  
        rec += pInterval  
    return rec

#Expression:
autoIncrement()

Un exemple de ce que j'essaie de faire. J'ai utilisé un tri avancé pour trier par année, mois, jour et je souhaite maintenant avoir des numéros séquentiels dans le Seqchamp. Vous verrez que mon OBJECTIDchamp n'est pas en ordre, donc le code ci-dessus ne fonctionnera pas.

entrez la description de l'image ici

Cela peut-il être fait dans la calculatrice de champ ou en utilisant un curseur de mise à jour dans arcpy?

Midavalo
la source
Dans ArcObjects avec un ITableSort, vous devriez pouvoir le faire .. pas tellement en python. Comment le tableau est-il trié? vous pouvez le lire dans un dictionnaire avec OID et un champ de tri, trier le dictionnaire, créer un autre dictionnaire avec OID et Value, itérer le premier dictionnaire trié pour attribuer la valeur au second, puis le curseur en l'attribuant avec le deuxième dictionnaire ... a un peu de déblayage mais c'est tout ce à quoi je peux penser sans utiliser ArcObjects.
Michael Stimson
@ MichaelMiles-Stimson ce n'est pas une mauvaise idée, je pourrais probablement le charger dans des dictionnaires pour déterminer un ordre de tri puis écrire ces valeurs dans la Seq.
Midavalo
C'est comme ça que je l'ai fait auparavant et ça a bien fonctionné. Je ne trouve pas mon code pour le moment; C'était unique, donc il est probablement sur l'un de mes disques de sauvegarde ... Si je le rencontre, je posterai comme réponse - à condition qu'il n'y ait pas déjà une bonne réponse à cette question.
Michael Stimson
J'ai toujours été ennuyé que cela ne puisse pas être fait facilement dans ArcGIS. Alors que c'est trivial dans MapInfo. La manière la plus simple que j'ai rencontrée est d'utiliser l' outil de tri, mais cela crée un autre ensemble de données que vous devrez rejoindre.
Fezter
Votre syntaxe python fonctionne parfaitement, merci pour cela. Je me demande simplement s'il est possible de commencer la première ligne avec 1 plutôt que 0. Si c'est possible, pouvez-vous me donner le code pour cela. Bon week-end Fred
Fred

Réponses:

13

"Solution" avec 2 champs triés (croissant):

mxd = arcpy.mapping.MapDocument("CURRENT")
lr=arcpy.mapping.ListLayers(mxd)[0]
tbl=arcpy.da.TableToNumPyArray(lr,("oid","A","B"))
bs=sorted(tbl, key=lambda x: (x[1], x[2]))
def sortSeq(fid,a,b):
 for i,ent in enumerate(bs):
   if ent[0]==fid: return i

--------------------------------------

sortSeq( !OID!, !A!, !B! )

entrez la description de l'image ici

VERSION MISE À JOUR:

mxd = arcpy.mapping.MapDocument("CURRENT")
lr=arcpy.mapping.ListLayers(mxd)[0]
tbl=arcpy.da.TableToNumPyArray(lr,("oid","A","B"))
bs=sorted(tbl, key=lambda x: (x[1], x[2]))
aDict={}
for i,row in enumerate(bs):
 aDict[row[0]]=i
def sortSeq(fid):
 return aDict[fid]

-----------------------

sortSeq( !OID!)

prend 1,5 seconde pour terminer la tâche sur 10000 enregistrements. L'original prend un peu plus de 2 minutes

FelixIP
la source
Je crois que les quatre premières lignes de ce code sont exécutées pour chaque enregistrement. Cela ne doit pas être autorisé, car la couche ne doit être triée qu'une seule fois pour tout le calcul. Pensez à utiliser l'astuce que je montre dans mon article ou à démontrer que la couche n'est lue qu'une seule fois pour déterminer l'ordre de tri des enregistrements uniquement pour le premier enregistrement.
Richard Fairhurst
@RichardFairhurst J'ai testé mon expression d'origine sur 10 milliers d'enregistrements, il a fallu 2 minutes 06 secondes pour terminer, la modification a entraîné une amélioration de 5 secondes. Il semble que les premières lignes ne soient pas répétées sur chaque disque. Oui, la calculatrice de terrain est beaucoup plus lente que le script, bien que pratique
FelixIP
Testez le même tableau par rapport à mon calcul. S'ils prennent pratiquement le même temps pour effectuer le calcul, j'accepterai votre hypothèse selon laquelle il n'est traité qu'une seule fois. 2 min et 6 secondes est assez lent.
Richard Fairhurst
OK 1,5 seconde semble indiquer que les 4 premières lignes ne sont pas traitées pour chaque enregistrement. Quoi qu'il en soit, le dictionnaire est la voie à suivre dans les deux cas. Cependant, que faites-vous lorsque je souhaite que le numéro Seq ne soit pas unique sur chaque enregistrement lorsque les valeurs des autres champs sont identiques? Ce serait ce que je voudrais pour le tableau associé dans une relation 1: M.
Richard Fairhurst
+1 @RichardFairhurst pour le dictionnaire. Parcourir ma liste était une partie lente de mon original. Re pas pour être unique c'est une grosse variation de OP
FelixIP
6

Il s'agit d'un processus en deux étapes et, par conséquent, la calculatrice de champ ne lui convient pas. Il est préférable de l'exécuter dans un script autonome. Cependant, cela peut être fait dans la calculatrice de terrain, à condition d'utiliser une astuce. Vous devez utiliser un curseur pour charger toutes les valeurs dans un dictionnaire global à partir d'une liste triée, mais uniquement lors du calcul du premier enregistrement. Pour tous les autres enregistrements, vous devez ignorer la création du dictionnaire pour éviter de relire constamment la table entière pour chaque ligne.

Les trois valeurs de champ doivent être placées dans un tuple pour agir comme une clé qui triera correctement. Je suppose que toutes les valeurs de combinaison à 3 champs sont uniques dans la table SamplePoint, mais j'ai ajouté l'ObjectID pour m'assurer qu'il est unique. Vous devez fournir le chemin et le nom du fichier de formes à la ligne 8 (ou je pourrais utiliser la technique que FelixIP utilise là où la première couche de la carte actuelle est utilisée). Si vous souhaitez utiliser différents champs pour une clé, vous devez modifier la liste des champs à la ligne 10 et les faire correspondre avec les champs d'entrée à la ligne 3 et à la ligne 15.

#Pre-logic Script Code:
relateDict = {}
def autoIncrement(myYear, myMonth, myDay, OID): 
    global relateDict  
    # only populate the dictionary if it has no keys  
    if len(relateDict) == 0:  
        # Provide the path to the relate feature class/table  
        relateFC = r"C:\Users\OWNER\Documents\ArcGIS\SamplePoints.shp"  
        # create a field list with the relate fields in sort order  
        relateFieldsList = ["Year", "Month", "Day", "OID@"]  
        # process a da search cursor to transfer the data to the dictionary  
        relateList = sorted([(r[0:]) for r in arcpy.da.SearchCursor(relateFC, relateFieldsList)])
        for relateSort in range(0, len(relateList)):
            relateDict[relateList[relateSort]] = relateSort + 1
    return relateDict[(myYear,myMonth,myDay,OID)]    

#Expression:
autoIncrement(!Year!, !Month!, !Day!, !OBJECTID!)

Je ne recommanderais pas non plus d'utiliser les noms de champs Année, Mois et Jour, car ceux-ci ne fonctionnent que dans les fichiers de formes et ne sont pas autorisés dans les géodatabases. Une géodatabase changera les noms en Year_1, Month_1, Day_1 si vous essayez de les ajouter à la liste des champs dans les propriétés de la table.

Si le but de ce tableau est de le relier à une autre table / classe d'entités sur une clé multi-champs, envisagez d'utiliser l'outil que j'ai créé dans mon blog nommé Clé de champ multiple à l'outil de clé de champ unique - Relier deux couches basées sur plusieurs. Champ

Richard Fairhurst
la source
Comment gère-t-il les doublons?
FelixIP
Ajoutez l'OID à la liste des champs. J'ai ajouté l'OID à la liste des champs pour m'assurer qu'il est unique.
Richard Fairhurst
Sinon, s'il existe des doublons et que l'utilisateur souhaite que tous les doublons aient la même valeur SEQ, laissez de côté l'ObjectID et utilisez set () dans la liste avant d'exécuter la boucle for et de l'ajouter au dictionnaire.
Richard Fairhurst
+1 Merci @RichardFairhurst, à peu près la même chose que ma tentative d'écrire en arcpy, même si je ne savais pas que vous pouviez appeler la plupart de cela à partir de la calculatrice de terrain
Midavalo
2

J'avais la même question mais pour un problème plus simple, basé sur le fait d'avoir un seul champ à trier. J'ai réussi avec le script suivant:

# Pre-Logic Script Code:
# Specify that the target Map Document is the current one
mxd = arcpy.mapping.MapDocument("CURRENT")
# Specify that the target layer is the first layer in the table of 
# content
lr=arcpy.mapping.ListLayers(mxd)[0]

tbl=arcpy.da.TableToNumPyArray(lr,("fid","Name_of_sorted_Field"))
bs=sorted(tbl,key=lambda x: x[1])
aDict={}
for i,row in enumerate(bs):
 aDict[row[0]]=i
def sortSeq(fid):
 return aDict[fid]

---------------------------------------------------------------
# to run the code, the following goes in the expression window
sortSeq(!FID!)
user122347
la source