Select to view content in your preferred language

Add Ordinal Letter to Cell Value

848
7
10-08-2013 10:01 AM
GeoffOlson
Regular Contributor
I need to add a letter to the end of a field name that increases for the selection set.  I have two shapefiles I'm working with of mosaiced tiles.  My script selects a large tile, copies the name value, then selects the smaller tiles from the second shapefile and assigns it the copied name.  But I need each of the smaller tiles to have a letter appended on so they each have different names.  It is possible to use a variable on a list to incrementally go to the next letter?  Here's my script, and it works with just adding the letter "A" to the end.

import arcpy, os

#set map doc and the layer to be used
mxd = arcpy.mapping.MapDocument("Current")
mapLyr1 = arcpy.mapping.ListLayers(mxd, "NEW_BiState_Grid400_IowaSP") [0]
mapLyr2 = arcpy.mapping.ListLayers(mxd, "GridIndexFeatures20") [0]

#alpha will be assigned a letter to rows2 update, there are 16
alpha = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']
name = str()
searchrow = 0
rows1 = arcpy.SearchCursor(mapLyr1, "", "", "Name")
rows2 = arcpy.UpdateCursor(mapLyr2, "", "", "tile")
rows = arcpy.UpdateCursor(mapLyr2)
for row in rows1:
    name = row.getValue("Name")
    print name
    arcpy.SelectLayerByAttribute_management(mapLyr1, "NEW_SELECTION", '"FID" = %s' %searchrow)
    searchrow = searchrow + 1
    arcpy.SelectLayerByLocation_management(mapLyr2, "HAVE_THEIR_CENTER_IN", mapLyr1, 0, "ADD_TO_SELECTION")
    for row in rows2:
        row.tile = name + A
        rows2.updateRow(row)
del mxd, rows, rows1, rows2, searchrow
Tags (2)
0 Kudos
7 Replies
MattSayler
Frequent Contributor
Looping through the characters you want to use is potentially safer, slightly.

There are a few other ways, but they rely on incrementing the character code numbers, which could potentially bring unexpected results, i.e. wrong letter. That's probably not likely to happen though, as the codes for the latin alphabet should be pretty consistent for western character sets, but worth being aware of.

This post has some options:
http://stackoverflow.com/questions/11827226/can-we-increase-a-lowercase-character-by-one

What do you want to do if you hit the end of the list? Repeat? Double up, i.e. 'aa'?
0 Kudos
GeoffOlson
Regular Contributor
Thanks.  That's what I ended up doing.  But I didn't realize you could use a defined variable as the index number in the list.  I thought it was necessary to format the line with something like alpha[%d] %letterplace.  Here's my final script, but it is taking FOREVER to run through all the features.

import arcpy, os

#set map doc and the layer to be used
mxd = arcpy.mapping.MapDocument("Current")
mapLyr1 = arcpy.mapping.ListLayers(mxd, "NEW_BiState_Grid400_IowaSP") [0]
mapLyr2 = arcpy.mapping.ListLayers(mxd, "NEW_BiState_Grid100_IowaSP") [0]

#alpha will be assigned a letter to rows2 update, there are 16
place = 0
alpha = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P']
name = str()
searchrow = 0
rows1 = arcpy.SearchCursor(mapLyr1, "", "", "Name")
rows2 = arcpy.UpdateCursor(mapLyr2, "", "", "tile")
rows = arcpy.UpdateCursor(mapLyr2)
for row in rows1:
    name = row.getValue("Name")
    print name
    arcpy.SelectLayerByAttribute_management(mapLyr1, "NEW_SELECTION", '"FID" = %s' %searchrow)
    searchrow = searchrow + 1
    arcpy.SelectLayerByLocation_management(mapLyr2, "HAVE_THEIR_CENTER_IN", mapLyr1, 0, "ADD_TO_SELECTION")
    for row in rows2:
        row.tile = name + alpha[place]
        rows2.updateRow(row)
        place = place + 1
        if place == 16:
            place = 0
del mxd, rows, rows1, rows2, searchrow


Is it necessary to define "row in rows2:" as "row2 in rows2" since row is in use by rows1?  I thought "row" was a keyword used by arcpy, because I think trying "row2 in rows2" was giving me an error.
0 Kudos
MattSayler
Frequent Contributor
Thanks.  That's what I ended up doing.  But I didn't realize you could use a defined variable as the index number in the list.  I thought it was necessary to format the line with something like alpha[%d] %letterplace.  Here's my final script, but it is taking FOREVER to run through all the features.

import arcpy, os

#set map doc and the layer to be used
mxd = arcpy.mapping.MapDocument("Current")
mapLyr1 = arcpy.mapping.ListLayers(mxd, "NEW_BiState_Grid400_IowaSP") [0]
mapLyr2 = arcpy.mapping.ListLayers(mxd, "NEW_BiState_Grid100_IowaSP") [0]

#alpha will be assigned a letter to rows2 update, there are 16
place = 0
alpha = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P']
name = str()
searchrow = 0
rows1 = arcpy.SearchCursor(mapLyr1, "", "", "Name")
rows2 = arcpy.UpdateCursor(mapLyr2, "", "", "tile")
rows = arcpy.UpdateCursor(mapLyr2)
for row in rows1:
    name = row.getValue("Name")
    print name
    arcpy.SelectLayerByAttribute_management(mapLyr1, "NEW_SELECTION", '"FID" = %s' %searchrow)
    searchrow = searchrow + 1
    arcpy.SelectLayerByLocation_management(mapLyr2, "HAVE_THEIR_CENTER_IN", mapLyr1, 0, "ADD_TO_SELECTION")
    for row in rows2:
        row.tile = name + alpha[place]
        rows2.updateRow(row)
        place = place + 1
        if place == 16:
            place = 0
del mxd, rows, rows1, rows2, searchrow

Few things:
Is it necessary to define "row in rows2:" as "row2 in rows2" since row is in use by rows1?  I thought "row" was a keyword used by arcpy, because I think trying "row2 in rows2" was giving me an error.

'row' is just a (local) variable name. You can use just about whatever you want, 'item', 'thing', 'chair'. You will want to use something different for each cursor. Right now the 'row' in the second 'for' statement is probably walking on the 'row' from the first. The error you were getting must have been due to something else. What was the error message?
0 Kudos
GeoffOlson
Regular Contributor
The problem with row in the nested loop was that it didn't match the row.Update(row) part a couple lines down.  The interpreter was just giving me a 99999 error with no description.  Here's my working script.  The only problem is that my percent calculation doesn't work for some reason.  I don't know why, but it always just says 2.00000 percent regardless of what it looks like the calculation should be.  This took several hours to run so that's why I included the AddMessage lines to each main loop.

import arcpy, os

#set map doc and the layer to be used
mxd = arcpy.mapping.MapDocument("Current")
mapLyr1 = arcpy.mapping.ListLayers(mxd, "NEW_BiState_Grid400_IowaSP") [0]
mapLyr2 = arcpy.mapping.ListLayers(mxd, "NEW_BiState_Grid100_IowaSP") [0]

#alpha will be assigned a letter to rows2 update, there are 16
place = 0
alpha = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P']
searchrow = 0
rows1 = arcpy.SearchCursor(mapLyr1, "", "", "Name")
rowcount = int(arcpy.GetCount_management(mapLyr1).getOutput(0))
allrows = (rowcount + 0.0)
for row in rows1:
    bigtile = str()
    arcpy.SelectLayerByAttribute_management(mapLyr1, "NEW_SELECTION", '"FID" = %s' %searchrow)
    bigtile = row.getValue("Name")
    print bigtile
    searchrow = searchrow + 1
    prgrow = (searchrow + 0.0)
    arcpy.SelectLayerByLocation_management(mapLyr2, "HAVE_THEIR_CENTER_IN", mapLyr1, 0, "ADD_TO_SELECTION")
    rows2 = arcpy.UpdateCursor(mapLyr2, "", "", "", "FID")
    for row2 in rows2:
        row2.tile = bigtile + alpha[place]
        rows2.updateRow(row2)
        place = place + 1
        if place == 16:
            place = 0
    arcpy.SelectLayerByAttribute_management(mapLyr1, "CLEAR_SELECTION")
    arcpy.SelectLayerByAttribute_management(mapLyr2, "CLEAR_SELECTION")
    prgrss = ((prgrow / allrows)*100.0)
    arcpy.AddMessage("%d current row" %searchrow)
    arcpy.AddMessage("%d total rows" %rowcount)
    arcpy.AddMessage("%f percent completed" %prgrss)
    arcpy.AddMessage("______________________________")
del mxd, row, rows1, row2, rows2, searchrow, place, bigtile, rowcount, prgrow, allrows


Edit: fixed variable prgss to match prgrss.
0 Kudos
MattSayler
Frequent Contributor
There are some built in classes for handling the progress bar, which might be easier and probably runs faster (printing to the message window has a lot of overhead). http://resources.arcgis.com/en/help/main/10.1/index.html#//018v0000003z000000

How many records are you working with?
0 Kudos
GeoffOlson
Regular Contributor
Thanks for the link.  This script is being used on a shapefile with ~8500 features to name the tiles (16x16) within each of those ~8500 features, so about 130,000 features are being calculated.  My script ran fine, it just took 8-10 hours.  I was looking into a time left message by getting the time at the beginning of each loop and again at the end, then multiplying that by the number of loop cycles left, but I'm using the Python deltatime function and can't figure out how to format it.  I tried

    clock1 = datetime.now() #beginning of loop
    clock2 = datetime.now() end of loop
    clock3 = ((clock2 - clock1) * rowsleft) # rowsleft is total records minus completed records
    clock4 = clock3.strftime('%d Days, %h Hours, %M Minutes, %S Seconds')


but I get an error that 'datetime.timedelta' object has no attribute 'strftime' so I'm not sure how to get that working.  Anyway, the script does run, it just took a really long time, so I learned it's nice to know when a long script is working properly versus being in an infinite loop.
0 Kudos
MattSayler
Frequent Contributor
Yeah, time left is a tough one since it's going to vary with resource usage. You can use the progressor to show percent complete at least. You can also set the message for the progressor, so you could do something like display a time stamp periodically to help keep tabs if it's still running.

I believe writing the progressor message is less overhead than writing to the window. Still might want to set it up so it updates every 100 ro 1,000 records instead of for every record though.
0 Kudos