4

I want to update the 'Start_End' field in the following shapefile attribute table:

enter image description here

The attribute table consists of 6,134 records. For every unique value in the 'Id' field, I'd like to update each 'Start_End' record with a string, either 'Start' or 'End', depending on the value of 'OID_copy'. For example, for every unique value of 'Id' with the smallest value in the 'OID_copy' field, the 'Start_End' field should be populated with the word 'Start'. Conversely, for every unique value of 'Id' with the largest value in the 'OID_copy' field, the 'Start_End' field should be populated with the word 'End'.

Note: both the 'Id' and the 'OID_copy' fields are integer types, and the 'Start_End' field is string.

My initial thought was that a dictionary and an update cursor would do the trick, but so far I have been unsuccessful at updating the attribute table. My code doesn't throw an error, furthermore, the dictionary seems to have been created correctly. It looks like {1: [0, 1], 2: [2, 3], 3: [4, 5], ...}. However, the 'Start_End' field doesn't get updated.

# Start_End_UpdateAttribute.py
# purpose: For each unique 'Id' value, populate the 'Start_End' field in the valley centerline points shapefile attribute table
# with "start" and "end" using the value of the 'OID_copy' field as criteria

import arcpy
import traceback

arcpy.env.workspace = r'D:/GIS_data/Regional/Washington/ValleyBottom_UC_SF/ValleyBottom_UC_SF_centerlines/Wenatchee'
fc = r'D:/GIS_data/Regional/Washington/ValleyBottom_UC_SF/ValleyBottom_UC_SF_centerlines/Wenatchee' \
     r'/VC_ChiwawaRiver_split_verts_Copy.shp'
fields = ['Id', 'OID_copy', 'Start_End']
idDict = {}
cursor = arcpy.da.SearchCursor(fc, fields)

try:
    for row in cursor:
        id = row[0]
        oid = row[1]
        stend = row[2]
        idDict.setdefault(id, []).append(oid)
except:
    print 'An error occurred'
    traceback.print_exc()
del cursor
print idDict  # output is {key: [value, value], key: [value, value], etc}

cursor2 = arcpy.da.UpdateCursor(fc, fields)

try:
    for row in cursor2:
        for k, v in idDict.items():
            if v == min(v):
                row[2] = "Start"
                cursor2.updateRow(row)
            else:
                row[2] = "End"
    print 'Updated \'Start_End\' field'
except:
    print 'An error occurred - part 2'
    traceback.print_exc()
del cursor2

How should the second block of my code be revised to produce an attribute table that looks like this?:

enter image description here

2
  • Will there ever be more than 2 integers of the same value in id?
    – Aaron
    Feb 28 '20 at 22:34
  • @Aaron No, the 'Id' field doesn't contain more than 2 records with the same integer. In other words, there are 3067 unique values in the 'Id' field Feb 28 '20 at 22:41
4

You have some problems in the update for. 1) I don't understand why you want to iterate the dictionary, you already have the key in row[0]. 2) This comparison v == min(v) it is always false v is an array and min(v) is an interger. 3) Related with 2, you are only updating on true case.

So this is a fix to your code,

# Start_End_UpdateAttribute.py
# purpose: For each unique 'Id' value, populate the 'Start_End' field in the valley centerline points shapefile attribute table
# with "start" and "end" using the value of the 'OID_copy' field as criteria

import arcpy
import traceback

arcpy.env.workspace = r'D:/GIS_data/Regional/Washington/ValleyBottom_UC_SF/ValleyBottom_UC_SF_centerlines/Wenatchee'
fc = r'D:/GIS_data/Regional/Washington/ValleyBottom_UC_SF/ValleyBottom_UC_SF_centerlines/Wenatchee' \
     r'/VC_ChiwawaRiver_split_verts_Copy.shp'
fields = ['Id', 'OID_copy', 'Start_End']
idDict = {}
cursor = arcpy.da.SearchCursor(fc, fields)

try:
    for row in cursor:
        id = row[0]
        oid = row[1]
        stend = row[2]
        idDict.setdefault(id, []).append(oid)
except:
    print 'An error occurred'
    traceback.print_exc()
del cursor
print idDict  # output is {key: [value, value], key: [value, value], etc}

cursor2 = arcpy.da.UpdateCursor(fc, fields)

try:
    for row in cursor2:
        v = idDict[row[0]]
        if row[1] == min(v):
            row[2] = "Start"
        else:
            row[2] = "End"
        cursor2.updateRow(row)
    print 'Updated \'Start_End\' field'
except:
    print 'An error occurred - part 2'
    traceback.print_exc()
del cursor2
2
  • thank you for finding the errors in my code, as well as describing the errors. Your corrections worked! I am just learning how to use dictionaries and update cursors, and I greatly appreciate your assistance. Feb 29 '20 at 17:26
  • Glad it helps you!.
    – cabesuon
    Feb 29 '20 at 18:38
2

You can use pandas library. If your table is sorted by Id and OID_copy you dont need to use a dictionary:

import arcpy
import pandas as pd

fc = r'C:\GIS\data.gdb\points'
fields = ['Id','Start_End']
df = pd.DataFrame.from_records(data=arcpy.da.SearchCursor(fc,fields), columns=fields) #Create pandas dataframe using da.SearchCursor

l = df.groupby(fields[0]).cumcount().tolist() #https://stackoverflow.com/questions/23435270/how-to-add-sequential-counter-column-on-groups-using-pandas-groupby
l = ['Start' if x%2==0 else 'End' for x in l] #If even cumcount number 'Start' else 'End'

with arcpy.da.UpdateCursor(fc, fields[-1]) as cursor:
    for row, startend in zip(cursor, l):
        row[0] = startend
        cursor.updateRow(row)

enter image description here

1
  • 1
    thanks for recommending an alternative approach to solving my problem. I have not used the pandas library, but I am looking forward to learning how to use it. Feb 29 '20 at 18:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.