Существует ли простой способ извлечь N первых слов из локального макроса, разделенного запятой или пробелом запятая в стате?

#string #stata #local-variables #stata-macros

Вопрос:

Учитывая локальный макрос, содержащий строку уровней, разделенных запятой ( » ,») или запятой и пробелом ( » ,») или даже только пробелом ( «» ), существует ли простой способ извлечь первые N уровней (или слов) этого локального макроса?

Строка будет выглядеть как "12, 123, 1321, 41" , или "12,123,1321,41" или "12 123 1321 41" .

В принципе, я был бы доволен версией макрофункции word # of string , которая работала бы более или менее похоже word 1/N of string . (См. «Макрофункции для синтаксического анализа» на стр. 12 в разделе Определение и управление макросами)

Для большего контекста я работаю с выводом levelsof, local() sep() . Таким образом, я могу выбрать разделитель, с которым будет легче работать. Я хочу передать полученные уровни в качестве аргумента inlist() функции. Обычно работает следующее, но inlist() требуется всего до 250 аргументов. Вот почему я хотел бы извлечь фрагменты из 250 слов результатов levelsof()

 sysuse auto, clear
levelsof mpg if trunk > 20, local(levels) sep(", ")
list if inlist(mpg, `levels')
 

«решение» до сих пор

Я придумал не простой способ добиться этого, но он выглядит не очень хорошо, и мне интересно, есть ли простой встроенный способ сделать то же самое.

 sysuse auto, clear

levelsof mpg if trunk > 20, local(levels) sep(", ")
scalar number_of_words = 3
forvalues i = 1 (1) `=number_of_words' {
        local word_i = `i'
        local this_level : word `word_i' of `levels'
        local list_of_levels = "`list_of_levels'`this_level'" 
        
        di as text "loop: `i'"
        di as text "this level: `this_level'"
        di as text "list of levels so far: `list_of_levels'"
    }

di "`list_of_levels'"

// trim trailing comma
local trimmed_list_of_levels = substr( "`list_of_levels'" , 1 , strlen( "`list_of_levels'" )-1) 

di "`trimmed_list_of_levels'"
list make mpg price trunk if inlist(mpg, `trimmed_list_of_levels')
 

выход

 . sysuse auto, clear
(1978 Automobile Data)

. 
. levelsof mpg if trunk > 20, local(levels) sep(", ")
12, 15, 17, 18

. scalar number_of_words = 3

. forvalues i = 1 (1) `=number_of_words' {
  2.         local word_i = `i'
  3.         local this_level : word `word_i' of `levels'
  4.         local list_of_levels = "`list_of_levels'`this_level'" 
  5.         
.         di as text "loop: `i'"
  6.         di as text "this level: `this_level'"
  7.         di as text "list of levels so far: `list_of_levels'"
  8.     }
loop: 1
this level: 12,
list of levels so far: 12,
loop: 2
this level: 15,
list of levels so far: 12,15,
loop: 3
this level: 17,
list of levels so far: 12,15,17,

. 
. di "`list_of_levels'"
12,15,17,

. 
. // trim trailing comma
. local trimmed_list_of_levels = substr( "`list_of_levels'" , 1 , strlen( "`list_of_levels'" )-1) 

. 
. di "`trimmed_list_of_levels'"
12,15,17

. list make mpg price trunk if inlist(mpg, `trimmed_list_of_levels')

      ------------------------------------------ 
     | make                mpg    price   trunk |
     |------------------------------------------|
  2. | AMC Pacer            17    4,749      11 |
  5. | Buick Electra        15    7,827      20 |
 23. | Dodge St. Regis      17    6,342      21 |
 26. | Linc. Continental    12   11,497      22 |
 27. | Linc. Mark V         12   13,594      18 |
     |------------------------------------------|
 31. | Merc. Marquis        15    6,165      23 |
 53. | Audi 5000            17    9,690      15 |
 74. | Volvo 260            17   11,995      14 |
      ------------------------------------------ 
 

правки, касающиеся комментариев.

правка 01)

Например, следующее не работает. Он возвращает ошибку 130 expression too long .

 clear 

set obs 1000
gen id = _n 
gen x1 = rnormal()

sum * 
levelsof id if x1>0, local(levels) sep(", ")
sum * if inlist(id, `levels')
 

пример, где эта конструкция (levelsof inlist) кажется необходимой

 clear 

set obs 5000
gen id = round(_n/5)
gen x1 = rnormal()

sum * 
levelsof id if x1>2, local(levels) sep(", ")
sum * if x1>2 // if threshold is small enough, there will be too many values for inlist()
sum * if inlist(id, `levels')
 

Комментарии:

1. Вы можете использовать word команду в цикле, где word будет возвращено n-е слово в списке (см. help word ). Можете ли вы подробнее рассказать о том, каков ваш окончательный план на будущее inlist ? Возможно, есть более простой способ сделать это.

2. Спасибо за быстрый ответ. См. правку 1 в вопросе. если существует более (около) 250 уровней, функция inlist возвращает error 130``expression too long значение . Когда их меньше, это работает нормально. word Функция извлекает только одно слово из строки. Я блуждал, есть ли встроенный модуль для извлечения n первого слова s , который позволит избежать использования этого цикла, который я написал в вопросе. Однако цикл работает, если нет встроенного или более простого способа.

3. Быстрый ответ: Вставьте локальный в Mata и используйте результат tokens() .

4. Ответ Ника лучше всего подходит для вашего вопроса. С моей точки зрения, трудно увидеть конечную цель, которая потребовала бы этого sum * if x1>0 в отношении вашего редактирования 1.

5. Правда, я добавлю пример, где имеет значение пройти этот маршрут.. Представьте , что у меня есть несколько наблюдений одного и того же id , не обязательно с одним и тем же x1 . Если я хочу извлечь все наблюдения (предыдущие и прошлые) из тех id s, которые хотя бы однажды имели x1 значение больше порогового значения , то, насколько мне известно, мне нужно будет прибегнуть к этой levelsof inlist конструкции .

Ответ №1:

Используя ваш дополнительный пример в качестве основы, вы могли бы использовать egen max для создания флага, равного 1 для всего id , у которого есть любые случаи, когда x1 значение превышает определенный порог. Например:

 clear 
set seed 2021
set obs 5000
gen id = round(_n/5)
gen x1 = rnormal()

sum * 
levelsof id if x1>2, local(levels) sep(", ")
sum * if x1>2 // if threshold is small enough, there will be too many values for inlist()
sum * if inlist(id, `levels')

//This will do the same thing
gen over_threshold = x1>2 
egen id_over_thresh = max(over_threshold), by(id)

sum * if id_over_thresh
 

Комментарии:

1. Для использования в будущем вы можете сократить его до egen id_over_thresh = max(x1>2), by(id)

2. очень интересная идея! Я посмотрю, применимо ли это к моим фактическим случаям использования, которые немного сложнее, и операторы if основаны на нескольких переменных и условиях… (включая числовые и категориальные данные…). В любом случае, хотя это может даже решить мою проблему, оно не адресует вопрос напрямую…

3. Обратите внимание, что здесь x1 будет >2, если оно отсутствует.

4. Да, Ник правильно указал на пропажи. Я согласен, что это не дает прямого ответа на ваш вопрос в том виде, в каком он есть, нажатие на mata это-лучший короткий путь, который я могу придумать. Приведенный выше ответ должен быть достаточно хорошо обобщен, независимо от сложности условий или любого количества идентифицирующих переменных в by группе.

5. Спасибо JR96. Он действительно работает с моими фактическими данными, и ваше решение очень гибкое. Довольно простое, но элегантное решение моей проблемы…