1
votes

Observations in my data set contain the history of moves for each player. I would like to count the number of consecutive series of moves of some pre-defined length (2, 3 and more than 3 moves) in the first and the second halves of the game. The sequences cannot overlap, i.e. the sequence 1111 should be considered as a sequence of the length 4, not 2 sequences of length 2. That is, for an observation like this:

+-------+-------+-------+-------+-------+-------+-------+-------+
| Move1 | Move2 | Move3 | Move4 | Move5 | Move6 | Move7 | Move8 |
+-------+-------+-------+-------+-------+-------+-------+-------+
|     1 |     1 |     1 |     1 | .     | .     |     1 |     1 |
+-------+-------+-------+-------+-------+-------+-------+-------+

…the following variables should be generated:

Number of sequences of 2 in the first half =0 
Number of sequences of 2 in the second half =1
Number of sequences of 3 in the first half =0
Number of sequences of 3 in the second half =0
Number of sequences of >3 in the first half =1 
Number of sequences of >3 in the second half = 0

I have two potential options of how to proceed with this task but neither of those leads to the final solution:

Option 1: Elaborating on Nick’s tactical suggestion to use strings (Stata: Maximum number of consecutive occurrences of the same value across variables), I have concatenated all “move*” variables and tried to identify the starting position of a substring:

egen test1 = concat(move*)
gen test2 = subinstr(test1,"11","X",.) // find all consecutive series of length 2

There are several problems with Option 1: (1) it does not account for cases with overlapping sequences (“1111” is recognized as 2 sequences of 2) (2) it shortens the resulting string test2 so that positions of X no longer correspond to the starting positions in test1 (3) it does not account for variable length of substring if I need to check for sequences of the length greater than 3.

Option 2: Create an auxiliary set of variables to identify the starting positions of the consecutive set (sets) of the 1s of some fixed predefined length. Building on the earlier example, in order to count sequences of length 2, what I am trying to get is an auxiliary set of variables that will be equal to 1 if the sequence of started at a given move, and zero otherwise:

+-------+-------+-------+-------+-------+-------+-------+-------+
| Move1 | Move2 | Move3 | Move4 | Move5 | Move6 | Move7 | Move8 |
+-------+-------+-------+-------+-------+-------+-------+-------+
|     0 |     0 |     0 |     0 |     0 |     0 |     1 |     0 |
+-------+-------+-------+-------+-------+-------+-------+-------+

My code looks as follows but it breaks when I am trying to restart counting consecutive occurrences:

quietly forval i = 1/42 {
gen temprow`i' =.
egen rowsum = rownonmiss(seq1-seq`i') //count number of occurrences
replace temprow`i'=rowsum 
mvdecode seq1-seq`i',mv(1) if rowsum==2 
drop rowsum
}

Does anyone know a way of solving the task?

1

1 Answers

1
votes

Assume a string variable concatenating all moves all (the name test1 is hardly evocative).

FIRST TRY: TAKING YOUR EXAMPLE LITERALLY

From your example with 8 moves, the first half of the game is moves 1-4 and the second half moves 5-8. Thus there is for each half only one way to have >3 moves, namely that there are 4 moves. In that case each substring will be "1111" and counting reduces to testing for the one possibility:

gen count_1_4 = substr(all, 1, 4) == "1111"
gen count_2_4 = substr(all, 5, 4) == "1111" 

Extending this approach, there are only two ways to have 3 moves in sequence:

gen count_1_3 = inlist(substr(all, 1, 4), "111.", ".111")  
gen count_2_3 = inlist(substr(all, 5, 4), "111.", ".111")  

In similar style, there can't be two instances of 2 moves in sequence in each half of the game as that would qualify as 4 moves. So, at most there is one instance of 2 moves in sequence in each half. That instance must match either of two patterns, "11." or ".11". ".11." is allowed, so either includes both. We must also exclude any false match with a sequence of 3 moves, as just mentioned.

gen count_1_2 = (strpos(substr(all, 1, 4), "11.") | strpos(substr(all, 1, 4), ".11") ) & !count_1_3 
gen count_2_2 = (strpos(substr(all, 5, 4), "11.") | strpos(substr(all, 5, 4), ".11") ) & !count_2_3 

The result of each strpos() evaluation will be positive if a match is found and (arg1 | arg2) will be true (1) if either argument is positive. (For Stata, non-zero is true in logical evaluations.)

That's very much tailored to your particular problem, but not much worse for that.

P.S. I didn't try hard to understand your code. You seem to be confusing subinstr() with strpos(). If you want to know positions, subinstr() cannot help.

SECOND TRY

Your last code segment implies that your example is quite misleading: if there can be 42 moves, the approach above can not be extended without pain. You need a different approach.

Let's suppose that the string variable all can be 42 characters long. I will set aside the distinction between first and second halves, which can be tackled by modifying this approach. At its simplest, just split the history into two variables, one for the first half and one for the second and repeat the approach twice.

You can clone the history by

  clonevar work = all 
  gen length1 = . 
  gen length2 = . 

and set up your count variables. Here count_4 will hold counts of 4 or more.

  gen count_4 = 0 
  gen count_3 = 0 
  gen count_2 = 0 

First we look for move sequences of length 42, ..., 2. Every time we find one, we blank it out and bump up the count.

  qui forval j = 42(-1)2 { 
       replace length1 = length(work) 
       local pattern : di _dup(`j') "1" 
       replace work = subinstr(work, "`pattern'", "", .) 
       replace length2 = length(work) 
       if `j' >= 4 {
            replace count4 = count4 + (length1 - length2) / `j' 
       }
       else if `j' == 3 { 
            replace count3 = count3 + (length1 - length2) / 3
       }
       else if `j' == 2 { 
            replace count2 = count2 + (length1 - length2) / 2 
       }
  }

The important details here are

  1. If we delete (repeated instances of) a pattern and measure the change in length, we have just deleted (change in length) / (length of pattern) instances of that pattern. So, if I look for "11" and found that the length decreased by 4, I just found two instances.

  2. Working downwards and deleting what we found ensures that we don't find false positives, e.g. if "1111111" is deleted, we don't find later "111111", "11111", ..., "11" which are included within it.

  3. Deletion implies that we should work on a clone in order not to destroy what is of interest.