Classify values

970
13
09-02-2011 07:47 AM
MatthiasAbele
New Contributor II
Hallo,

I ve got a column with values between 0 - 2. I would like to classify the values in classes between 1 - 5 according to the following scheme. The class for each value should be written in another column.

Class 1   0.0 - 0.2
Class 2   0.2 - 0.4
Class 3   0.4 - 0.6
Class 4   0.6 - 0.8
Class 5   0.8 - 1.0
Class 4   1.0 - 1.2
Class 3   1.2 - 1.4
Class 2   1.4 - 1.6
Class 1   1.6 - 9.0

How can I do this with python for several feature classes?

Thanx for any hints,

Matthias
Tags (2)
0 Kudos
13 Replies
JakeSkinner
Esri Esteemed Contributor
You could write something similar to the code below.  You will probably have to add a try/except clause to pass feature classes that do not contain the corresponding fields.

import arcpy
from arcpy import env
env.workspace = r"C:\temp\python\test.gdb"

lstFCs = arcpy.ListFeatureClasses("*")
for fc in lstFCs:
    rows = arcpy.UpdateCursor(fc)
    try:
        for row in rows:
            if row.Value >= 0.0 and row.Value <= 0.2:
                row.Class = 1
            if row.Value > 0.2 and row.Value <= 0.4:
                row.Class = 2
            if row.Value > 0.4 and row.Value <= 0.6:
                row.Class = 3
            if row.Value > 0.6 and row.Value <= 0.8:
                row.Class = 4
            if row.Value > 0.8 and row.Value <= 1.0:
                row.Class = 5
            if row.Value > 1.0 and row.Value <= 1.2:
                row.Class = 4
            if row.Value > 1.2 and row.Value <= 1.4:
                row.Class = 3
            if row.Value > 1.4 and row.Value <= 1.6:
                row.Class = 2
            if row.Value > 1.6 and row.Value <= 2.0:
                row.Class = 1
            rows.updateRow(row)
    except RuntimeError:
        pass

del row, rows  
0 Kudos
StacyRendall1
Occasional Contributor III
Assuming your value field is Value and your class (output) field is ClassField. If you are using it in a Field Calculator:
Pre-Logic Script Code:
classBreaks = [0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 2] # defines where the values for the breaks are
classVals = [1, 2, 3, 4, 5, 4, 3, 2, 1] # what should be returned - corresponds to the position of the value in classBreaks

def calc(x): # function that does the calculation
 '''calc(x) iterates through the classBreaks until it finds the bounding values of the input number, then returns the corresponding value from classVals
 '''
 for i in xrange(len(classBreaks)-1):
  if classBreaks <= x < classBreaks[i+1]:
   return classVals
                elif x == classBreaks[-1]: # x equals the last value - wouldn't work above, as it is less than...
                        return classVals[-1]
 return None # return something if the value isn't within the range, can be whatever you want - None will be converted to Arc NULL, but you could have 'No Class' or something like that, if you wanted...

ClassField=
calc(!Value!)


Or as part of a Python script:
import arcpy

classBreaks = [0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 2] # defines where the values for the breaks are
classVals = [1, 2, 3, 4, 5, 4, 3, 2, 1] # what should be returned - corresponds to the position of the value in classBreaks

def calc(x): # function that does the calculation
 '''calc(x) iterates through the classBreaks until it finds the bounding values of the input number, then returns the corresponding value from classVals
 '''
 for i in xrange(len(classBreaks)-1):
  if classBreaks <= x < classBreaks[i+1]:
   return classVals
                elif x == classBreaks[-1]: # x equals the last value - wouldn't work above, as it is less than...
                        return classVals[-1]
 return None # return something if the value isn't within the range, can be whatever you want - None will be converted to Arc NULL, but you could have 'No Class' or something like that, if you wanted...

arcpy.env.workspace = r"C:\temp\python\test.gdb"

listFCs = arcpy.ListFeatureClasses("*")
for fc in listFCs:
    rows = arcpy.UpdateCursor(fc)
    for row in rows:
        row.ClassField = calc(row.Value)
        rows.updateRow(row)

del row, rows
0 Kudos
MatthiasAbele
New Contributor II
Hallo,

thanx for the code; worked very good with some test data; but if I apply it to my real data I get the error: Runtime error <type 'exceptions.NameError'>: name 'row' is not defined

What could be the reason? I properly changed the path and the field names...

Yours,

Matthias
0 Kudos
StacyRendall1
Occasional Contributor III
Was it my code or Jakes you were using? Either way, my guess it that it is failing at the very last line - deleting the variables. For some reason in my experience that occasionally has issues.

Try replacing:
del row, rows

with:
try: del row
except NameError: pass
del rows


Let me know how you get on.
0 Kudos
MatthiasAbele
New Contributor II
Hi StacyRendall,

it worked now. But I still had problems. The file names of my feature classes were very long and contained some numbers. When I reduced the file name lengths and erased the numbers, it worked. So, thanx a lot.

I would like to make a little script from the code.

First I would like to define the input geodatabase:

Geodatabase =  gp.GetParameterAsText(0) # Location of Geodatabase

Now, I am a little bit stuck. I would like to read in the class breaks as a comma separated list
and also the corresponding class values. How is this possible


Then I would define the input / calculation row as:

value =  gp.GetParameterAsText(3) # Values to be classified
classfield = gp.GetParameterAsText(4) # Field to be calculated

Is this the right way?

Yours

Matthias
0 Kudos
StacyRendall1
Occasional Contributor III
Sweet; you are off to a good start. It is actually quite easy to bring in the values as comma separated strings, but you will need to bring the breaks and the break values is as separate parameters. I.e.:
# presumably you are bringing in other variables, you only mentioned 0 then jumped to 3 - the parameter numbers must go up in order...
classBreaks_str = gp.GetParameterAsText(3) # Break values (comma separated values)
classValues_str = gp.GetParameterAsText(4) # Classes (comma separated values) to be returned, must correspond with the Break values
valueField = gp.GetParameterAsText(5) # Field to be calculated on
classField = gp.GetParameterAsText(6) # Field for results to go in


Now, we will convert them both to look like lists, but stored as strings, then use the Python evaluate command, eval(), to turn them into lists. Eval parses strings and outputs them literally. Continuing on from above:
classBreaks_str = '[' + classBreaks_str + ']' # add square brackets, so they look like lists
classValues_str = '[' + classValues_str + ']'

try: classBreaks = eval(classBreaks_str)
except SyntaxError: arcpy.AddError('Class Breaks was entered incorrectly; values must each seperated by a comma.')
except NameError: arcpy.AddError('Class Breaks was entered incorrectly; values must either be numbers or, if strings, within quotes.')

try: classValues = eval(classValues_str) # I called this classVals in the previous post
except SyntaxError: arcpy.AddError('Class Values was entered incorrectly; values must each seperated by a comma.')
except NameError: arcpy.AddError('Class Values was entered incorrectly; values must either be numbers or, if strings, within quotes.')

if len(classBreaks) != len(classValues):
 arcpy.AddError('Class Values and Class Breaks must be the same length.')
 
# rest of processing (previous posts) goes here...


The classValues is classVals from my previous post, but classBreaks is the same... I also added a few things to catch common errors; they will cause an Arcpy Error to appear in the processing results window.

Let me know how you get on.
0 Kudos
MatthiasAbele
New Contributor II
Hi Stacy,

thanx, my understanding for python is getting better an better, here is the code...it is not running so far...

import arcpy
import arcgisscripting
gp = arcgisscripting.create()


Feature_Class = gp.GetParameterAsText(0) # Location of Geodatabase
classBreaks_str = gp.GetParameterAsText(1) # Break values (comma separated values)
classValues_str = gp.GetParameterAsText(2) # Classes (comma separated values) to be returned, must correspond with the Break values
valueField = gp.GetParameterAsText(3) # Field to be calculated on
classField = gp.GetParameterAsText(4) # Field for results to go in

classBreaks_str = '[' + classBreaks_str + ']' # add square brackets, so they look like lists
classValues_str = '[' + classValues_str + ']'

try: classBreaks = eval(classBreaks_str)
except SyntaxError: arcpy.AddError('Class Breaks was entered incorrectly; values must each seperated by a comma.')
except NameError: arcpy.AddError('Class Breaks was entered incorrectly; values must either be numbers or, if strings, within quotes.')

try: classValues = eval(classValues_str) # I called this classVals in the previous post
except SyntaxError: arcpy.AddError('Class Values was entered incorrectly; values must each seperated by a comma.')
except NameError: arcpy.AddError('Class Values was entered incorrectly; values must either be numbers or, if strings, within quotes.')

if len(classBreaks) != len(classValues):
 arcpy.AddError('Class Values and Class Breaks must be the same length.')

def calc(x): # function that does the calculation
 '''calc(x) iterates through the classBreaks until it finds the bounding values of the input number, then returns the corresponding value from classVals
 '''
 for i in xrange(len(classBreaks)-1):
  if classBreaks <= x < classBreaks[i+1]:
   return classValuess
                elif x == classBreaks[-1]: # x equals the last value - wouldn't work above, as it is less than...
                        return classValues[-1]
 return None # return something if the value isn't within the range, can be whatever you want - None will be converted to Arc NULL, but you could have 'No Class' or something like that, if you wanted...


listFCs = arcpy.ListFeatureClasses("*")
for fc in listFCs:
    rows = arcpy.UpdateCursor(fc)
    for row in rows:
        row.classField = calc(row.valueField)
        rows.updateRow(row)

try: del row
except NameError: pass
del rows


This is the error message:

<type 'exceptions.RuntimeError'>: Row: Field valueField does not exist
Failed to execute (Classify)


In the Parameter's dialogue, I configured the input as feature class and value- and classfield as field. But if I start the script and choose a fc, it doesnt read out the available columns (fields)? Is the parameter configuration wrong?
If I want to loop through several feature classes and I define as input workspace or feature dataset, I have to input the valuefield and classfield names as string. Is this possible by just setting the parameter type of the script properties to string?

Yours,

Matthias
0 Kudos
StacyRendall1
Occasional Contributor III
OK, Arc can list the field types, it is just a little tricky. When you set the parameters, for the field parameter you need to set the Filter to field and the Obtained from as the parameter name you gave to your layer. As long as you select your layer before finding the field, it should work fine.

There is one other thing I missed that will still cause it to fail. When using a cursor there are two ways you can access a value in a field; either row.getValue('fieldname') or row.fieldname. The problem with the latter method is that your field name has to be hard-coded - so you can't easily make it an input parameter. There is a similar distinction when setting the value of a field; either row.setValue('fieldname', value) or row.fieldname = value.

Some other things:

  • you don't need to import arcgisscripting or worry about the gp bits - that was Arc 9 stuff.

  • you need to list your feature classes based on the Geodatabase location, by setting the environment workspace to the geodatabase.

  • the del row and rows statements need to be within the feature class iterator (i.e. indented), as you want to clear them out once you are done with each feature class.


I'm pretty sure that this now should work just fine:
import arcpy

def calc(x): # function that does the calculation
 '''calc(x) iterates through the classBreaks until it finds the bounding values of the input number, then returns the corresponding value from classVals
 '''
 for i in xrange(len(classBreaks)-1):
  if classBreaks <= x < classBreaks[i+1]:
   return classValues
  elif x == classBreaks[-1]: # x equals the last value - wouldn't work above, as it is less than...
   return classValues[-1]
 return None # return something if the value isn't within the range, can be whatever you want - None will be converted to Arc NULL, but you could have 'No Class' or something like that, if you wanted...

Geodatabase = arcpy.GetParameterAsText(0) # Location of Geodatabase
classBreaks_str = arcpy.GetParameterAsText(1) # Break values (comma separated values)
classValues_str = arcpy.GetParameterAsText(2) # Classes (comma separated values) to be returned, must correspond with the Break values
valueField = arcpy.GetParameterAsText(3) # Field to be calculated on
classField = arcpy.GetParameterAsText(4) # Field for results to go in

classBreaks_str = '[' + classBreaks_str + ']' # add square brackets, so they look like lists
classValues_str = '[' + classValues_str + ']'

try: classBreaks = eval(classBreaks_str)
except SyntaxError: arcpy.AddError('Class Breaks was entered incorrectly; values must each seperated by a comma.')
except NameError: arcpy.AddError('Class Breaks was entered incorrectly; values must either be numbers or, if strings, within quotes.')

try: classValues = eval(classValues_str)
except SyntaxError: arcpy.AddError('Class Values was entered incorrectly; values must each seperated by a comma.')
except NameError: arcpy.AddError('Class Values was entered incorrectly; values must either be numbers or, if strings, within quotes.')

if len(classBreaks) != len(classValues):
 arcpy.AddError('Class Values and Class Breaks must be the same length.')

arcpy.env.workspace = Geodatabase # set the workspace

listFCs = arcpy.ListFeatureClasses('*')
for fc in listFCs:
 rows = arcpy.UpdateCursor(fc)
 for row in rows:
  row.setValue(classField, calc(row.getValue(valueField))) # looks complex, but is just getting the value from valueField, passing it to CALC then writing the output to the classField
  rows.updateRow(row)

 try: del row
 except NameError: pass
 del rows
0 Kudos
MatthiasAbele
New Contributor II
Hi StacyRendall,

thank you a lot for the hints...I was able to make the scirpt work.


Yours

Matthias
0 Kudos