5  运算符和表达式

运算符和表达式的用途主要有两个方面:一是与generatereplace等命令搭配使用,用于生成新的变量或者改变变量的取值;二是和if连用构成if语句,将命令的作用范围限定在特定的行(观测值)。

5.1 运算概览

在Stata中,运算符可以分为四大类,分别是代数运算、字符运算、逻辑运算和关系运算。

代数运算(符)包括:

符号 含义
+
-
*
/
^ 指数

字符运算(符)包括:

符号 含义
+ 字符合并

逻辑运算(符)包括:

符号 含义
!
~
&
|

关系运算(符)包括:

符号 含义
< 小于
>= 大于或等于
<= 小于或等于
!= 不等于
~= 不等于

为了避免优先级带来的困扰,应当用括号将优先级顺序表示出来,位于最里层括号内的表达式将被优先执行。

5.2 代数运算

代数运算包括:加(+)、减(-)乘(*)、除(/)、幂指数()、负数。当遇到缺失值的运算或者结果无意义(比如除以0或者对非正数取对数)的代数运算结果均为缺失值(missingvalue)。

例如我们可以使用代数运算符号结合display做一些简单的计算:

di 4-2 // di 是display命令的简写
di 3*5
di 8/2
di 2^3
di ln(2)
di sqrt(9)

. di 4-2 // di 是display命令的简写
2

. di 3*5
15

. di 8/2
4

. di 2^3
8

. di ln(2)
.69314718

. di sqrt(9)
3

. 

也可以计算一些稍微复杂的结果,例如当x=4, y=2,求解如下表达式的取值(结果为-1):

-\frac{x+y^{x-y}}{xy}

di -1*(4+2^(4-2))/(4*2)  
. di -1*(4+2^(4-2))/(4*2)  
-1

对于上面的例子,我们还可以利用Stata中的scalar(即标量)来做一些符号替换的运算

scalar x = 4 
scalar y = 2
disp -1*(x+y^(x-y))/(x*y) 

. scalar x = 4 

. scalar y = 2

. disp -1*(x+y^(x-y))/(x*y) 
-1

. 
标量和数据、变量不同

注意,标量scalr以及相关的概念localmacro我们会在@sec-control 《流程控制语句》中继续介绍。但有一点需要注意,scalarlocalmacro都是独立于变量和二维表数据的存在,它们不会像变量一样出现在数据二维表中,想要查看它们的取值,必须通过display命令。

除了直接针对数字进行运算外,更常见的是在变量间进行运算,或者说运算符也可以作用在变量之间,用于生成新的变量

clear
sysuse auto, clear 
generate turn2 = turn^2
gen ln_turn = ln(turn)
gen sqrt_turn2 = sqrt(turn2)
gen turn_p1 = turn+1

. clear

. sysuse auto, clear 
(1978 automobile data)

. generate turn2 = turn^2

. gen ln_turn = ln(turn)

. gen sqrt_turn2 = sqrt(turn2)

. gen turn_p1 = turn+1

. 
变量出现在表达式中

当Stata中的变量出现在generate命令赋值号右侧的表达式exp中的时候(例如上面的generate turn2 = turn^2),Stata中的处理实质上是一种“逐行操作”的思路:新建一个新的变量turn2 \rightarrow 对于第一行,用变量第一行的取值代入表达式,运算结果填入turn2变量的第一行 \rightarrow 然后是第二行、第三行、依次类推,最终到最后一行。

5.3 字符运算

加号(+)同样可以用在字符运算,当加号出现在两个字符或者两个字符型变量之间时,两个字符(变量)将会连接成一个字符(变量)变量之间,用于生成新的变量:

scalar a = "我喜欢使用" + "Stata" + "工作学习" // scalar是标量,在后续章节中会介绍
scalar list a 

clear 
set obs 3 
gen y = "y"
replace y = "y1" in 1 
replace y = "y2" in 3 
replace y = "y3" in 2 
list

clear 
set obs 3
gen y = "y"
gen x = _n
tostring x, replace 
gen yy = y+x
describe 
list

. scalar a = "我喜欢使用" + "Stata" + "工作学习" // scalar是标量,在后续章节中
> 会介绍

. scalar list a 
         a = 我喜欢使用Stata工作学习

. 
. clear 

. set obs 3 
Number of observations (_N) was 0, now 3.

. gen y = "y"

. replace y = "y1" in 1 
variable y was str1 now str2
(1 real change made)

. replace y = "y2" in 3 
(1 real change made)

. replace y = "y3" in 2 
(1 real change made)

. list

     +----+
     |  y |
     |----|
  1. | y1 |
  2. | y3 |
  3. | y2 |
     +----+

. 
. clear 

. set obs 3
Number of observations (_N) was 0, now 3.

. gen y = "y"

. gen x = _n

. tostring x, replace 
x was float now str1

. gen yy = y+x

. describe 

Contains data
 Observations:             3                  
    Variables:             3                  
-------------------------------------------------------------------------------
Variable      Storage   Display    Value
    name         type    format    label      Variable label
-------------------------------------------------------------------------------
y               str1    %9s                   
x               str1    %9s                   
yy              str2    %9s                   
-------------------------------------------------------------------------------
Sorted by: 
     Note: Dataset has changed since last saved.

. list

     +------------+
     | y   x   yy |
     |------------|
  1. | y   1   y1 |
  2. | y   2   y2 |
  3. | y   3   y3 |
     +------------+

. 

需要指出的是,虽然加法可以用在代数运算和字符运算中,但不能将数字与字符相加(因为这样没有任何意义)。

5.4 关系运算

关系运算主要是判断大小关系、相等关系是否成立。包括:大于、大于或等于(不小于)、等于、小于或等于、小于、不等于。注意:在Stata中的符号是两个等号==1

di 3 > 5 // 结果是0,在Stata中,逻辑真(True)用1表示,逻辑假(False)用0来表示
di 3 <= 5 // 结果是1
// assert 3 > 5 
assert 3 <= 5 // 请通过help assert查看assert命令的含义
di 3==4 // 结果是0
di 3 != 4
di 3==3 // 结果是1

. di 3 > 5 // 结果是0,在Stata中,逻辑真(True)用1表示,逻辑假(False)用0来表
> 示
0

. di 3 <= 5 // 结果是1
1

. // assert 3 > 5 
. assert 3 <= 5 // 请通过help assert查看assert命令的含义

. di 3==4 // 结果是0
0

. di 3 != 4
1

. di 3==3 // 结果是1
1

. 

也可以将逻辑判断的结果(0或1)赋值给变量:

clear 
set obs 5 
gen x = 4<5 
gen y = (3==5)
gen x1 = _n 
gen y1 = mod(_n,2)
gen z1 = x1==y1 

. clear 

. set obs 5 
Number of observations (_N) was 0, now 5.

. gen x = 4<5 

. gen y = (3==5)

. gen x1 = _n 

. gen y1 = mod(_n,2)

. gen z1 = x1==y1 

. 

当数据中存在缺失值的时候,需要特别注意。因为Stata中缺失值(missing value)设定为大于任何一个数据,我们可以利用这一特点设定条件来筛选数据

例:将下面的数据按照65岁作为分界点分成两组。缺失值显然不应该包含在任何一个分组中。

age
38
.
65
42
18
80
clear
input age 
38 
.
65
42
18
80
end
list, sep(0)

gen agegroup1 = (age>=65)
gen agegroup2 = (age>=65) if age <.
gen agegroup3 = (age<65) if age < . 
list 

. clear

. input age 

           age
  1. 38 
  2. .
  3. 65
  4. 42
  5. 18
  6. 80
  7. end

. list, sep(0)

     +-----+
     | age |
     |-----|
  1. |  38 |
  2. |   . |
  3. |  65 |
  4. |  42 |
  5. |  18 |
  6. |  80 |
     +-----+

. 
. gen agegroup1 = (age>=65)

. gen agegroup2 = (age>=65) if age <.
(1 missing value generated)

. gen agegroup3 = (age<65) if age < . 
(1 missing value generated)

. list 

     +--------------------------------------+
     | age   agegro~1   agegro~2   agegro~3 |
     |--------------------------------------|
  1. |  38          0          0          1 |
  2. |   .          1          .          . |
  3. |  65          1          1          0 |
  4. |  42          0          0          1 |
  5. |  18          0          0          1 |
     |--------------------------------------|
  6. |  80          1          1          0 |
     +--------------------------------------+

. 
list命令

list, sep(0)这行代码是在命令list的基础上加入了seperator的option,可以在list输出结果的时候不打印分隔横线。大家可以通过help list查看对应的帮助文档。也可以对比一下和list(不加sep(0)这一option)的结果又什么不同。

5.5 逻辑运算

逻辑运算包括:或(|)、与(&)、非(! or ~)三种。

例如,下面的例子基于auto.dta示例数据,列示出了价格大于1万美元的所有车或者小于4000元的国产车。

sysuse auto,clear 
// 注意为了避免优先级影响结果,复杂的逻辑运算应当用括号保证运算逻辑和你的预期相符
br if (price > 10000) | ((price < 4000) &(foreign == 0)) 

当然也可以用于生成新的变量

仍然用上面给年龄数据分组的例子,可以用如下逻辑运算进行改写:

clear
input age 
38 
.
65
42
18
80
end
list, sep(0)
gen group = . 
replace group = 1 if (age < .) & (age >=65) 
replace group = 2 if (age < .) & (age <65)
tab group
list 

. clear

. input age 

           age
  1. 38 
  2. .
  3. 65
  4. 42
  5. 18
  6. 80
  7. end

. list, sep(0)

     +-----+
     | age |
     |-----|
  1. |  38 |
  2. |   . |
  3. |  65 |
  4. |  42 |
  5. |  18 |
  6. |  80 |
     +-----+

. gen group = . 
(6 missing values generated)

. replace group = 1 if (age < .) & (age >=65) 
(2 real changes made)

. replace group = 2 if (age < .) & (age <65)
(3 real changes made)

. tab group

      group |      Freq.     Percent        Cum.
------------+-----------------------------------
          1 |          2       40.00       40.00
          2 |          3       60.00      100.00
------------+-----------------------------------
      Total |          5      100.00

. list 

     +-------------+
     | age   group |
     |-------------|
  1. |  38       2 |
  2. |   .       . |
  3. |  65       1 |
  4. |  42       2 |
  5. |  18       2 |
     |-------------|
  6. |  80       1 |
     +-------------+

. 
思考

在上面的代码段中,replace group = 2 if (age < .) & (age <65)这句代码中的两个条件age < .age <65有一个是多余的(在当前的数据和逻辑下可以省略一个,应该是哪一个?

5.6 分组运算

在“命令结构”的章节中,我们曾介绍过分组bysort varlist的作用是将数据集按照varlist的取值分成若干个组,每个组内的分别执行命令。我们首先模拟生成一组数据,其中有两个变量:idx

clear 
input id x 
1 1.5
1 1.6
1 1.7
2 2.5 
2 2.6 
2 2.7
3 3.5 
3 3.6 
3 3.7 
end 
list 
save "temp.dta", replace 

. clear 

. input id x 

            id          x
  1. 1 1.5
  2. 1 1.6
  3. 1 1.7
  4. 2 2.5 
  5. 2 2.6 
  6. 2 2.7
  7. 3 3.5 
  8. 3 3.6 
  9. 3 3.7 
 10. end 

. list 

     +----------+
     | id     x |
     |----------|
  1. |  1   1.5 |
  2. |  1   1.6 |
  3. |  1   1.7 |
  4. |  2   2.5 |
  5. |  2   2.6 |
     |----------|
  6. |  2   2.7 |
  7. |  3   3.5 |
  8. |  3   3.6 |
  9. |  3   3.7 |
     +----------+

. save "temp.dta", replace 
file temp.dta saved

. 

我们可以通过如下命令,实现在针对不同id取值对应的x,进行描述性统计:

use "temp.dta", clear 
// 根据id取值对数据分组,每个分组内分别执行summarize
bysort id: summarize x 

. use "temp.dta", clear 

. // 根据id取值对数据分组,每个分组内分别执行summarize
. bysort id: summarize x 

-------------------------------------------------------------------------------
-> id = 1

    Variable |        Obs        Mean    Std. dev.       Min        Max
-------------+---------------------------------------------------------
           x |          3         1.6          .1        1.5        1.7

-------------------------------------------------------------------------------
-> id = 2

    Variable |        Obs        Mean    Std. dev.       Min        Max
-------------+---------------------------------------------------------
           x |          3         2.6          .1        2.5        2.7

-------------------------------------------------------------------------------
-> id = 3

    Variable |        Obs        Mean    Std. dev.       Min        Max
-------------+---------------------------------------------------------
           x |          3         3.6          .1        3.5        3.7


. 

我们还可以将bysort_n联合起来使用,用于生成组内的自增序列,以及打印显示每个组内的第一条数据:

use "temp.dta", clear 
// 
// 生成自增序列(变量i),以及在每个分组内生成自增序列(变量j)
gen i = _n 
bysort id: gen j = _n 
list 
//
// 列出每个分组内的第一行数据,可以有两种方法:
list if j == 1
bysort id: list if _n == 1 

. use "temp.dta", clear 

. // 
. // 生成自增序列(变量i),以及在每个分组内生成自增序列(变量j)
. gen i = _n 

. bysort id: gen j = _n 

. list 

     +------------------+
     | id     x   i   j |
     |------------------|
  1. |  1   1.5   1   1 |
  2. |  1   1.6   2   2 |
  3. |  1   1.7   3   3 |
  4. |  2   2.5   4   1 |
  5. |  2   2.6   5   2 |
     |------------------|
  6. |  2   2.7   6   3 |
  7. |  3   3.5   7   1 |
  8. |  3   3.6   8   2 |
  9. |  3   3.7   9   3 |
     +------------------+

. //
. // 列出每个分组内的第一行数据,可以有两种方法:
. list if j == 1

     +------------------+
     | id     x   i   j |
     |------------------|
  1. |  1   1.5   1   1 |
  4. |  2   2.5   4   1 |
  7. |  3   3.5   7   1 |
     +------------------+

. bysort id: list if _n == 1 

-------------------------------------------------------------------------------
-> id = 1

     +------------------+
     | id     x   i   j |
     |------------------|
  1. |  1   1.5   1   1 |
     +------------------+

-------------------------------------------------------------------------------
-> id = 2

     +------------------+
     | id     x   i   j |
     |------------------|
  1. |  2   2.5   4   1 |
     +------------------+

-------------------------------------------------------------------------------
-> id = 3

     +------------------+
     | id     x   i   j |
     |------------------|
  1. |  3   3.5   7   1 |
     +------------------+


. 

也可以将包含_n的表达式与bysort连用,用于在每个分组内修改变量的取值

use "temp.dta", clear 
// 修改变量的取值
gen y1 = x+_n 
bysort id: gen y2 = x+_n
list 

. use "temp.dta", clear 

. // 修改变量的取值
. gen y1 = x+_n 

. bysort id: gen y2 = x+_n

. list 

     +-----------------------+
     | id     x     y1    y2 |
     |-----------------------|
  1. |  1   1.5    2.5   2.5 |
  2. |  1   1.6    3.6   3.6 |
  3. |  1   1.7    4.7   4.7 |
  4. |  2   2.5    6.5   3.5 |
  5. |  2   2.6    7.6   4.6 |
     |-----------------------|
  6. |  2   2.7    8.7   5.7 |
  7. |  3   3.5   10.5   4.5 |
  8. |  3   3.6   11.6   5.6 |
  9. |  3   3.7   12.7   6.7 |
     +-----------------------+

. 

在Stata中,变量在特定行的取值可以使用varname[#]的形式获取,下面的代码就将变量x特定行的数值放入了表达式中参与运算:

use "temp.dta", clear 
gen z1 = _n+x[1]
bysort id: gen z2 = _n+x[1]
list 

. use "temp.dta", clear 

. gen z1 = _n+x[1]

. bysort id: gen z2 = _n+x[1]

. list 

     +-----------------------+
     | id     x     z1    z2 |
     |-----------------------|
  1. |  1   1.5    2.5   2.5 |
  2. |  1   1.6    3.5   3.5 |
  3. |  1   1.7    4.5   4.5 |
  4. |  2   2.5    5.5   3.5 |
  5. |  2   2.6    6.5   4.5 |
     |-----------------------|
  6. |  2   2.7    7.5   5.5 |
  7. |  3   3.5    8.5   4.5 |
  8. |  3   3.6    9.5   5.5 |
  9. |  3   3.7   10.5   6.5 |
     +-----------------------+

. 

5.7 习题

1.[计算分段函数]。下面的例子生成了一个随机序列 x ,取值为1-10之间的整数。

clear 
set obs 10 
gen x = int(unifrom()*10)+1
list x

请按照如下定义,生成 y 变量,可以尝试尽可能多的方法来实现。 y=\left\{ \begin{aligned} 1.5x, & 0<x<=5 \\ 2x, & 5<x=10 \end{aligned} \right.

2.[Stata中_n的妙用与缺失值处理]。下面的代码模拟生成了1个国家10年间的GDP增长率数据,其中有年份(2015年)的数据缺失。据中包括如下变量: country_id:国家代码 year:年份 GDPG:国家的GDP增长率

clear all
set obs 10
set seed 10000
gen country_id = 1
gen year = _n+2010 
gen GDPG = round(uniform()/5,0.001)
replace GDPG = . if year == 2015
  1. 请在上面代码的基础上,编写代码,实现如下两种缺失值处理的思路:
  1. 生成GDPG1,将GDP变量中的缺失值用本国前一年的数据填充替换;
  2. 生成GDPG2,将GDP变量中的缺失值用本国前一年和后一年的平均值填充;
  3. 生成GDPG3,将GDP变量中的缺失值用其他年份的平均值填充(提示:需要用到下一个章节中的均值函数)

(提示:变量在特定行的取值可以使用varname[#]的形式获取,其中#为行号。例如GDPG[1]就是变量GDPG在第一行的取值;_n代表的是当前行号,GDPG[_n]GDPG[_n-1]可以获取当前GPDG变量的当前行与上一行取值)

  1. 如果将上述数据集扩充到的200个国家的20年GDP增长率数据,同样存在一些国家一些年份(但这些年份不一定是在2015年)的GDP增长率数据缺失。下面的代码模拟生成了这些数据:
clear all
set obs 4000
set seed 10000
gen country_id = mod(_n,200)+1
sort country_id 
codebook country_id
bysort country_id: gen year = _n+2002
gen random = uniform()
gen GDPG = round(uniform()/5,0.001)
sort country_id random 
bysort country_id: replace GDPG = . if _n == 1
sort country_id year 
drop random 

请编写代码,实现如下两种缺失值处理的思路:

  1. 生成GDPG1,将GDP变量中的缺失值用本国前一年的数据填充替换;
  2. 生成GDPG2,将GDP变量中的缺失值用本国前一年和后一年的平均值填充;
  3. 生成GDPG3,将GDP变量中的缺失值用本国其他年份的平均值填充(提示:需要用到下一个章节中的均值函数)

(提示:可以使用bysort分组操作功能)


  1. 单个等号是赋值号,和generate, egen, replace等命令连用,在编程语言中是一个常见的设定。↩︎