# How to compute a new attribute based on changes in another attribute?

I am attempting to classify a set of gps time-encoded point data into behaviors based on different attributes. I have created an attribute that is 0 for home and 1 for away based on location, and now want to number the trips away from home (a set of points 01111111111110 would be one trip because it started and ended at home). I have added the attribute field that will have the trip numbers, but don't know how to calculate the field so it is based on the home/away field.

Here is an example of the GPS data (using "*" to indicate irrelevant information and simply indexing times as 1, 2, etc.), the "Home/Away" indicator described above, and the desired trip indicator, "Trip", which I need to compute:

Time Lat Lon Home/Away Trip
1   *   *         0    0
2   *   *         1    1
3   *   *         1    1
....
12   *   *         1    1
13   *   *         0    0
14   *   *         0    0
15   *   *         1    2
16   *   *         1    2
....
34   *   *         1    2
35   *   *         0    0
36   *   *         0    0
37   *   *         1    3
....


My data set is too large to manually go through and number each trip in the attribute table, so is there any way to calculate the field based on how the home/away attribute is ordered and each "clump" of away points is designated as a trip?

UPDATE: these are the bare bones of what Python code might look like (I'm not experienced with code).

### Expression:

trip = Reclass(!home!)


### Codeblock:

def Reclass(home):
if (home = 0):
return 0
elif (home = 1 and lastValue = 0):
return _(incremental numbering?)_
elif (home = 1 and lastValue = 1):
return lastValue


UPDATE:
After using matt wilkie's recommended script I've made some changes so that my first trip is number 1, my second is 2, etc etc. Here is the code modified from matt's:

import arcpy
rows = arcpy.UpdateCursor("test2")

trip = 0
for row in rows:
if row.home == 0:
prev = row.home
row.TRIP = trip
rows.updateRow(row)

elif row.home == 1 and prev == 0:
trip += 1
prev = row.home
row.TRIP = trip
rows.updateRow(row)
rows.next()

elif row.home == 1 and prev == 1:
prev = row.home
row.TRIP = trip
rows.updateRow(row)
rows.next()

row.TRIP = trip
rows.updateRow(row)

del row, rows


Then I just select for home = 0 and calculate my trip field back to 0. Neatly ordered trips. Thanks a ton everyone!

-
It might also be helpful to provide an example input and what the desired output would look like. –  blah238 Jun 8 '12 at 0:46

For this you can use UpdateCursor, which opens the feature class or table and steps through each record (row) incrementally.

The script below works on this test data

+-----------------------+
| Time| Home_Away|Trip  |
+-----|----------|------+
|  1  |  0       | <nul>|
|  2  |  1       | <nul>|
|  4  |  1       | <nul>|
|  5  |  0       | <nul>|
|  6  |  0       | <nul>|
|  7  |  1       | <nul>|
|  9  |  1       | <nul>|
| 12  |  1       | <nul>|
| 13  |  0       | <nul>|
+-----------------------+


.

import arcpy
fc = r'D:\s\py\pyscratch.gdb\gps_points'

# open the feature class and create the cursor
rows = arcpy.UpdateCursor(fc)

trip = 0
for row in rows:
if row.HOME_AWAY == 0:
trip += 1           # start of new trip, increment counter
row.TRIP = trip     # calc the TRIP field to be current trip#
rows.updateRow(row) # save
print "Trip %s started at %s" % (trip, row.TIME)

# keep cycling through records until HOME_AWAY is not 1
while row.HOME_AWAY == 1:
row.TRIP = trip
rows.updateRow(row)
rows.next() # move to next record

# this is for the trailing end of a trip, the second 0
# print "     %s ended at %s" % (trip, row.TIME)
row.TRIP = trip
rows.updateRow(row)

# remove programming objects and data locks
# the data itself is left alone
del row, rows


The trailing end of trip block is actually run for the beginning of a trip also, but since the trip counter is correct the double calc on the begin-trip-row doesn't matter . Uncomment the print statement in that block to see what I mean.

Python automatically adds an implicit rows.next() at the end for the for row in rows block.

This assumes data integrity. It will mess up if there are ever an odd number of zero Home/Away records in a row (000 or 00000). A trip that only consists of start and stop should be okay, e.g. a 3 trip sequence of 01..10 00 01..10 , where the spaces denote the gaps between trips. In other words, validate the results!

-
+1, You MUST do this in an update cursor. The CalculateField tool does not guarantee that the code block will be run only once, so the trip variable may be re-initialized any arbitrary number of times. –  Jason Scheirer Jun 8 '12 at 19:16
This works great in that all of my trips are assigned one number for all points in the trip, however all the points at home are given a new number( i.e. my data starts with points at home now numbered 1, 2, 3, ..... 136 and then my first trip is all labelled 137). It isn't a big deal because I can revert all "home" points to 0, but it would be nice if my trips started at 1 and were evenly number after that. Any advice? –  AlmaThom Jun 8 '12 at 22:55
@Alice, I didn't test, but all you should need to do is comment out or delete the row.TRIP = trip line in each of the two blocks which handle beginning and end of trip. (and, come to think of it, the rows.updateRow(row) which follow, as there's nothing to save there anymore.) –  matt wilkie Jun 9 '12 at 6:17
Sorted out the glitch! my script now has three parts: –  AlmaThom Jun 18 '12 at 18:25

The ArcGIS 10 help under "calculate field examples" shows you how to "Calculate the accumulative value of a numeric field." This will do the trick, provided the data are physically in the intended temporal order.

To apply it directly, invert your [Home/Away] indicator (subtract it from 1) so that "0" means "away" and "1" means "home". I call this [Away/Home] in the example below.

Compute its cumulative value--[Cumulative] in the example.

Add one and divide by two--[Trip] in the example (almost).

Finally, set [Trip] to zero for all the "home" records. Now the results agree with the example:

Time Lat Lon Home/Away Trip Away/Home Cumulative
1   *   *         0    0         1          1
2   *   *         1    1         0          1
3   *   *         1    1         0          1
....
12   *   *         1    1         0          1
13   *   *         0    0         1          2
14   *   *         0    0         1          3
15   *   *         1    2         0          3
16   *   *         1    2         0          3
....
34   *   *         1    2         0          3
35   *   *         0    0         1          4
36   *   *         0    0         1          5
37   *   *         1    3         0          5
....


For the record, here's the code taken from the ArcGIS 10 help. I modified it slightly so it would do every step at once: now you only need to run it. It should be clear where [Home/Away] gets inverted and where the "add 1, divide by 2" step occurs.

Expression:

acc(!Home/Away!)


Expression Type:

PYTHON_9.3


Code Block:

t=0
def acc(i):
global t
if t:
t += (1-i)
else:
t = 1
if i:
return (t+1)/2
else:
return 0

-
For any large number of records this will not work. The codeblock re-runs every few hundred thousand rows (along with a full garbage collect cycle) so t will get reset to 0 in seemingly random places. –  Jason Scheirer Jun 8 '12 at 19:17
Thanks, @Jason: I wasn't aware of that bug. That's a real show-stopper. <rant>I thought ArcGIS was supposed to scale up so that it's good for more than little toy problems?</rant> –  whuber Jun 8 '12 at 19:37
Not a bug, it's actually an implementation detail inherited from the VBScript implementation to try to minimize memory leaks (users appending to a list for every record but never actually using the list for anything, for example). I'm pretty sure I got rid of the refresh in 11 because it's non-obvious behavior, but I don't remember. –  Jason Scheirer Jun 8 '12 at 21:35
@Jason That's a new euphemism for me: "implementation detail." Other euphemisms are "feature" and "undocumented behavior." A rose by any other name... –  whuber Jun 9 '12 at 1:15
Here's how I see it, @Jason: the help page itself provides the code I presented. There is, therefore, an implicit assertion on ESRI's part that the code works. According to you, it does not; indeed, under your characterization it can fail significantly, silently, without warning, and unpredictably. That's not just a bug, it's the nastiest form of bug possible. A "periodic reset" is not a "fix," it's a kluge that only makes the situation worse IMHO. –  whuber Jun 9 '12 at 16:27