SAS编程实践---宏:从头手搓一个生成”基本描述性统计量表格“的宏

写在前面。

最近的两篇文章分别介绍了我编写下面这两个宏时整体的思路和方法。菜鸟小白一枚,很欢迎有大佬能够阅读并批评指点。

SAS编程实践---宏:按系统术语、首选术语和严重程度分层次计算受试者发生不良反应(AE)例数和例次

SAS编程实践---宏:按系统术语和首选术语分层次计算受试者发生不良反应(AE)例数和例次

文章中呈现的是最终的宏代码,但是在实际编写过程中,步骤顺序可能会和文章的结构顺序不同,毕竟写文章和写代码还是有区别的

代码编写时可能会经历,编写---替换修改---增添---替换修改---...,这样的步骤很多次才形成了最终的代码。

不过目前我坚持的编写宏程序的总体结构还是如之前的文章一样,也即:

*_1. pre-processing;
*_2.main statistical steps;
*_3 processing step of stat;
* _4.output steps;
    1. 预处理(宏变量的处理和输入数据集的预处理)
    1. 主要的统计步骤
    1. 统计步骤后的处理
    1. 输出步骤

基本原则

在实际操作中,最开始进行目标表格拆解分析模拟数据生成后,进行到宏程序代码编写步骤,会先进行上述第1步中的输入数据集的预处理第2步的统计步骤

这两步是处理数据的最核心的步骤,而其他的步骤,更多是为了进行表格拼接修改,使达到目标表格样式;或者基于SAS宏的”文本替换“原则进行的辅助步骤。

  • 说明一下,很多步骤使用PROC SQL语句会简单许多,但是本文中除了少量的宏变量的赋值会使用PROC SQL语句以外,还是以使用基础DATA步和过程步为主。

准备步骤

说了这么多,下文我便一步一步实践去编写一个”基本描述性统计量表格“的宏的编写。

目标表格拆解分析

1

图中是将要生成的目标表格的模板:

  1. 指明了数值型变量,进行核心统计步proc means需要计算的统计指标有哪些;需要注意nminssing,可能存在分析数据集里观测的缺失,所以我一般使用adsl数据集进行各组受试者数量的确定;

  2. 表明了在拼接步需要拼接成的样式,需要注意,小数点位数的保留问题,因此需要在核心步骤之外进行小数点位数保留问题的处理;

  3. 合计列是否计算,需要条件选择语句,以及相应的数据预处理和计算步骤;

  4. 指明了分组分组变量是必须存在的,且我一般尽量会处理为数值型

模拟数据生成

通过目标表格的拆解,明确了那些模拟数据需要那些变量。

分析数据集

分析数据集必须要包含了分组变量待统计分析的数值型变量

2

我使用如下代码生成模拟数据:

%let seed1 = 33333333;

data ad0;
    do ii = 1 to 3;
        armn = ii;
        arm = cats("第",put(ii, best.) ,"组");

        do jj = 1 to 100;
            usubjid =  cats( "X",put(ii, best.) ,"-", put(jj, z3.));

            if mod(jj,3) = 0 then
                SEX = "女";
            else SEX = "男";
            HEIGHT = round(ranuni(&seed1.)*30 + 155, 0.1);
            output;
        end;
    end;
run;

同时,人为地造成了若干条观测的缺失

data ad;
    set ad0;

    if jj in (2  13  17) and sex = "男" then
        delete;

    if jj in (12  7  24) and sex = "女" then
        delete;
run;

adsl数据集

3

adsl数据集主要用来计算受试者的数量,需要包含分组变量信息

data adsl;
    do ii = 1 to 3;
        armn = ii;
        arm = cats("第",put(ii, best.) ,"组");
        do jj = 1 to 100;
            usubjid =  cats( "X",put(ii, best.) ,"-", put(jj, z3.));
            output;
        end;
    end;
run;

宏编写步骤

模拟数据有了,明确了所需的统计指标,接下来就是先使用模拟数据把数据预处理统计的步骤写出来,生成目标表格

数据预处理和核心统计步骤

分组统计

  • 我们这个例子是进行身高的描述性统计,使用proc means根据分组变量统计HEIGHT
* _2.main statistical steps; ;
* _2.1 Calculation of target variable statistics;
* _2.1.1  by group ;
* _2.1.1.1 target variable;
proc means data=ad  noprint ;
var HEIGHT;
by armn ;
output out=_bygrpm0 N= n nmiss=nmiss mean=mean STD=std  median = mid  q1=q1 q3= q3  max=max min=min;
run;
  • 分组统计adsl数据集中受试者的数量,为了计算nmissing指标;
* _2.1.1.2  Calculation of the number of every group;
proc freq data=adsl ;
table armn / out=_bygrpnum;
run;

注意,这一步也可以使用proc sql进行计算然后赋值为宏变量会更加简单。

  • 合并上述两个数据集
* _2.1.1.3 combine ;
proc sort data=_bygrpm0; by armn;run;

data _bygrp;
merge _bygrpm0
_bygrpnum(drop =  percent);
by armn;
run;

分组统计目标变量到这里其实都准备好了。

合计列

接下来是合计列指标的计算;

  • 合计数据的预处理
*_1. pre-processing;
* _1.2.1 for sum column ;
data _adsum;
set ad;
armn = 999;
arm = "合计";
run;

data _adslall;
set adsl;
armn = 999;
arm = "合计";
run;
  • 合计列统计量的计算
* _2.main statistical steps; ;
* _2.2 Calculation of  sum column statistics;
* _2.2.1 Calculation of  sum column  of target variable statistics;
proc means data=_adsum  noprint ;
var HEIGHT;
by armn;
output out=_summ0(drop=_TYPE_)  N= n nmiss=nmiss mean=mean STD=std median = mid q1=q1 q3= q3  max=max min=min;
run;
  • 受试者总数,为了构建nmissing
* _2.2.2 Calculation of number of all subject;
proc freq data=_adslall noprint;
table armn / out=_sumnum;
proc sort ; by armn;
run;
  • 合并上述两个数据集
* _2.2.3 combine ;
proc sort data=_summ0; by armn;run;

data _sum;
merge _summ0
_sumnum(drop =  percent);
by armn;
run;

统计后处理-数据拼接

接下来的步骤,我一般会这样做,根据横向排列的统计量,使用字符拼接函数拼成所需的样式,然后使用proc transpose 进行重新的塑型生成目标样式

不过为了简便些,可以先把分组统计的和最后的合计列数据集先set,然后再进行后续步骤,在宏代码里,就涉及到条件判断了。文章中为了分步展示,我先赋值个宏变量(包装进宏时删除就好),拆开步骤单独展示。

判断是否计算合计列的宏参数我预设为rowsumyn,为了提醒自己,我会在日志中产生一个警告

%let rowsumyn = Y;


* _3 processing step of stat;
* _3.1 processing step of stat;
%if %sysfunc(upcase(&rowsumyn.)) = %str(Y) %then %do;
%put WARNING: 将计算合计列;

data _dt4comb;
set _bygrp
_sum;
run;
%end;

%else %do;
%put WARNING: 不计算合计列;
data _dt4comb;
set _bygrp;
run;
%end;
  • 接下来是拼接步骤
* _3.2 Variable construction of the target format;
data _combdt;
length  _1nnmiss _2meansd _3median _4q1q3  _5minmax  $200.;
set _dt4comb;
_1nnmiss =cats( strip(put(n ,8.0)) , "(",strip(put(count - n , 8.0)), ")");
_2meansd = cats( strip(put( round(mean , 0.01 ), 8.2) ) ,  "(" ,strip(put( round(std, 0.001), 8.3) )   , ")");
_3median =  cats( strip(put( round(mid , 0.01 ) , 8.2) ) );
_4q1q3 = cats( strip(put( round(q1 , 0.01 ) , 8.2) ) ,  "~" ,strip(put( round(q3 , 0.01 )  , 8.2) )   );
_5minmax = cats( strip(put(  round(min , 0.01 ) , 8.1) ) ,  "~" ,strip(put(   round(max , 0.01 ) , 8.1) )   );
keep armn  _1nnmiss  _2meansd  _3median   _4q1q3   _5minmax;
proc sort; by armn;
run;  

注意,这个步骤的代码是初步的,因为这里是硬性规定了位数的取舍,而实际上,是还有一步关于小数位数问题的宏变量的预处理

统计后处理-数据塑形

  • 之后通过2次数据塑形,形成目标表格的样式
* _3.3 transpose;
proc transpose data=_combdt  out=_trans1;
var   _1nnmiss  _2meansd  _3median   _4q1q3   _5minmax;
by armn;
proc sort; by _NAME_  ;
run;

proc transpose data=_trans1  out=_trans2 prefix= _grp;
id armn;
var  col1;
by _NAME_ ;
run;

如下图所示:

可以看到,和目标表格基本上只有细节上的差别的,这些差别的处理就是一些辅助步骤要解决的问题了。


替换并包装成宏

宏参数及替换

接下来就是确定宏参数名称,这个宏我限定如下这些参数:

%macro MeanT(
libin=,
dtin=,
adsl=,
var=,
grpvarn=,
rowsumyn=,
label=,
append=,
libout,
dtout=
);

替换这一步我就不展示。使用SAS EG工具栏-编辑-替换或者Ctrl + H进行代码替换

然后包装进宏里,试着运行一下。

注意到这一步,不要最后的4个参数,是可以运行成功的。

小数点位数

不同的统计指标要保留的小数点位数是不一样的,更和原始数据的位数有关。

我不知道不同公司是否要求有所不同,我依据我们公司的原则,限定如下:

-nnmissing都是整数部分;

  • minmax指标保留位数,和原始数据最大小数位数一致
  • 当原始数据最大小数位数(decmax)小于等于2时:mean、median、q1q3保留decmax + 1位;std保留decmax + 2位;
  • 当原始数据最大小数位数(decmax)等于3时:mean、median、q1q3保留decmax + 1位;std保留4位;
  • 当原始数据最大小数位数(decmax)大于3时:mean、median、q1、q3std均保留4位;

好的,根据这些原则,下面利用代码实现。

  • 待分析变量的小数位数宏变量
data _stdt0;
set &libin..&dtin.;
run;

data _adsl;
set &adsl.;
run;

* _1. pre-processing;
*  _1.1 decimals ;

data _dec;
set  _stdt0;
dec = length(scan(strip(put(&var., best.)),2,"."));
if not index(&var.,".") then dec = 0;
run;

proc sql ;
select max(dec) into: decmax 
from _dec
;
quit;
%put 最大小数位数:&decmax.;

通过上面的代码,将待分析变量的最大小数位数赋值给宏变量decmax

  • 拼接步骤代码的修改

这样子,上述拼接步骤的代码就可以按照小数点保留的原则进行修改了,修改后的代码如下:

* _3.2 Variable construction of the target format;
data _combdt;
length  _1nnmiss _2meansd _3median _4q1q3  _5minmax  $200.;
set _dt4comb;
_1nnmiss =cats( strip(put(n ,8.0)) , "(",strip(put(count - n , 8.0)), ")");
_5minmax = cats( strip(put(  round(min , 10**-( &decmax.) ) , 8.%eval( &decmax.)) ) ,  "~" ,strip(put(   round(max , 10**-( &decmax.) ) , 8.%eval( &decmax.)) ) );

%if &decmax. <= 2 %then %do;
_2meansd = cats( strip(put( round(mean , 10**-( &decmax. + 1) ),  8.%eval( &decmax. + 1) ) ) ,  "(" ,strip(put( round(std,  10**-( &decmax. + 2)),   8.%eval( &decmax. + 2)) )   , ")");
_3median =  cats( strip(put( round(mid ,  10**-( &decmax. + 1) ) ,   8.%eval( &decmax. + 1) ) ) );
_4q1q3 = cats( strip(put( round(q1 , 10**-( &decmax. + 1) ) ,  8.%eval( &decmax. + 1) ) ) ,  "~" ,strip(put( round(q3 ,  10**-( &decmax. + 1)  )  ,  8.%eval( &decmax. + 1) ) )   );
%end;

%if &decmax. = 3 %then %do;
_2meansd = cats( strip(put( round(mean , 10**-( &decmax. + 1) ),  8.%eval( &decmax. + 1) ) ) ,  "(" ,strip(put( round(std,  0.0001),   8.4) )   , ")");
_3median =  cats( strip(put( round(mid ,  10**-( &decmax. + 1) ) ,   8.%eval( &decmax. + 1) ) ) );
_4q1q3 = cats( strip(put( round(q1 , 10**-( &decmax. + 1) ) ,  8.%eval( &decmax. + 1) ) ) ,  "~" ,strip(put( round(q3 ,  10**-( &decmax. + 1)  )  ,  8.%eval( &decmax. + 1) ) )   );
%end;

%if &decmax. >3 %then %do;
_2meansd = cats( strip(put( round(mean , 0.0001 ),  8.4 ) ) ,  "(" ,strip(put( round(std,  0.0001),   8.4) )   , ")");
_3median =  cats( strip(put( round(mid ,  0.0001 ) ,   8.4 ) ) );
_4q1q3 = cats( strip(put( round(q1 ,0.0001 ) ,  8.4 ) ) ,  "~" ,strip(put( round(q3 ,  0.0001  )  ,  8.4 ) )   );
%end;
keep &grpvarn.  _1nnmiss  _2meansd  _3median   _4q1q3   _5minmax;
proc sort; by &grpvarn.;
run;  

列名的修改

*_3.4 rename column;
    proc contents data=_trans2 out=_info   noprint;

    proc sort;
        by varnum;
    run;

    proc sql   noprint;
        select count(distinct NAME) ,  NAME into: varn, :vnam1 -:vnam99 from _info;
    quit;

    data _&dtout.;
        set _trans2;
        %do ii = 1 %to &varn.;
            %let jj = %eval(&ii. - 1);
            rename &&vnam&ii. = C&jj.;
        %end;
    run;

label第一列的修改

在目标表格的第一行是我们统计的指标的名称,添加代码如下:

* _3.5 label ;
    data _row1;
        length C0 $200.;
        C0 = cats("&label.");
    run;

    data _&dtout._;
        set _row1 
            _&dtout.;
    if c0 = "_1nnmiss" then C0 = "n(nmisssing)";
    if c0 = "_2meansd" then C0 = "Mean(SD)";
    if c0 = "_3median" then C0 = "Median";
    if c0 = "_4q1q3" then C0 = "Q1~Q3";
    if c0 = "_5minmax" then C0 = "Min~Max";
    run;

append

有时候要统计的变量不止一个,而我们又想下一个跟在上一个之后,因此设置了一个append参数

* _3.6 append ;
%if %sysfunc(upcase(&append.)) = %str(Y) and  %sysfunc(exist(&dtout., data) )  %then %do;

data &dtout.;
set %if %sysfunc(exist(&dtout., data) ) %then &dtout.;  _&dtout._ ;
run;
%end;
%else %do;
%put WARNING: 未追加数据集;

data &dtout.;
set  _&dtout._ ;
run;

%end;

数据集输出和清除临时数据集

*  _4 output data ;
*  _4.1 output ;
data &libout..&dtout.;
set &dtout.;
run;

*  _4.2 remove temp datasets ;
proc datasets lib=work;
delete _:;
run;

总结

以上就是我写这个宏的最主要代码,还有一些小判断就不展示了。

完整代码如下:

%macro MeanT(
libin=,
dtin=,
adsl=,
var=,
grpvarn=,
rowsumyn=,
label =,
append=,
libout=,
dtout=
);


data _stdt0;
set &libin..&dtin.;
run;

data _adsl;
set &adsl.;
run;

* _1. pre-processing;
*  _1.1 decimals ;

data _dec;
set  _stdt0;
dec = length(scan(strip(put(&var., best.)),2,"."));
if not index(&var.,".") then dec = 0;
run;

proc sql ;
select max(dec) into: decmax 
from _dec
;
quit;
%put 最大小数位数:&decmax.;

* _1.2.1 for sum column ;
data _adsum;
set _stdt0;
&grpvarn. = 999;
arm = "合计";
run;

data _adslall;
set  _adsl;
&grpvarn. = 999;
arm = "合计";
run;

* _2.main statistical steps; ;
* _2.1 Calculation of target variable statistics;
* _2.1.1.1 by group;
proc means data=_stdt0  noprint ;
var &var.;
by &grpvarn. ;
output out=_bygrpm0 N= n nmiss=nmiss mean=mean STD=std  median = mid  q1=q1 q3= q3  max=max min=min;
run;


* _2.1.1.2 Calculation of number of every group;
proc freq data=_adsl noprint;
table &grpvarn. / out=_bygrpnum;
proc sort ; by &grpvarn.;
run;

* _2.1.1.3 combine ;
proc sort data=_bygrpm0; by &grpvarn.;run;

data _bygrp;
merge _bygrpm0
_bygrpnum(drop =  percent);
by &grpvarn.;
run;


* _2.main statistical steps; ;
* _2.2 Calculation of  sum column statistics;
* _2.2.1 Calculation of  sum column  of target variable statistics;
proc means data=_adsum  noprint ;
var &var.;
by &grpvarn.;
output out=_summ0(drop=_TYPE_)  N= n nmiss=nmiss mean=mean STD=std median = mid q1=q1 q3= q3  max=max min=min;
run;

* _2.2.2 Calculation of number of all subject;
proc freq data=_adslall noprint;
table &grpvarn. / out=_sumnum;
proc sort ; by &grpvarn.;
run;


* _2.2.3 combine ;
proc sort data=_summ0; by &grpvarn.;run;

data _sum;
merge _summ0
_sumnum(drop =  percent);
by &grpvarn.;
run;


* _3 processing step of stat;
* _3.1 processing step of stat;
%if %sysfunc(upcase(&rowsumyn.)) = %str(Y) %then %do;
%put WARNING: 将计算合计列;

data _dt4comb;
set _bygrp
_sum;
run;
%end;
%else %do;
%put WARNING: 不计算合计列;
data _dt4comb;
set _bygrp;
run;
%end;



* _3.2 Variable construction of the target format;
data _combdt;
length  _1nnmiss _2meansd _3median _4q1q3  _5minmax  $200.;
set _dt4comb;
_1nnmiss =cats( strip(put(n ,8.0)) , "(",strip(put(count - n , 8.0)), ")");
_5minmax = cats( strip(put(  round(min , 10**-( &decmax.) ) , 8.%eval( &decmax.)) ) ,  "~" ,strip(put(   round(max , 10**-( &decmax.) ) , 8.%eval( &decmax.)) ) );

%if &decmax. <= 2 %then %do;
_2meansd = cats( strip(put( round(mean , 10**-( &decmax. + 1) ),  8.%eval( &decmax. + 1) ) ) ,  "(" ,strip(put( round(std,  10**-( &decmax. + 2)),   8.%eval( &decmax. + 2)) )   , ")");
_3median =  cats( strip(put( round(mid ,  10**-( &decmax. + 1) ) ,   8.%eval( &decmax. + 1) ) ) );
_4q1q3 = cats( strip(put( round(q1 , 10**-( &decmax. + 1) ) ,  8.%eval( &decmax. + 1) ) ) ,  "~" ,strip(put( round(q3 ,  10**-( &decmax. + 1)  )  ,  8.%eval( &decmax. + 1) ) )   );
%end;

%if &decmax. = 3 %then %do;
_2meansd = cats( strip(put( round(mean , 10**-( &decmax. + 1) ),  8.%eval( &decmax. + 1) ) ) ,  "(" ,strip(put( round(std,  0.0001),   8.4) )   , ")");
_3median =  cats( strip(put( round(mid ,  10**-( &decmax. + 1) ) ,   8.%eval( &decmax. + 1) ) ) );
_4q1q3 = cats( strip(put( round(q1 , 10**-( &decmax. + 1) ) ,  8.%eval( &decmax. + 1) ) ) ,  "~" ,strip(put( round(q3 ,  10**-( &decmax. + 1)  )  ,  8.%eval( &decmax. + 1) ) )   );
%end;

%if &decmax. >3 %then %do;
_2meansd = cats( strip(put( round(mean , 0.0001 ),  8.4 ) ) ,  "(" ,strip(put( round(std,  0.0001),   8.4) )   , ")");
_3median =  cats( strip(put( round(mid ,  0.0001 ) ,   8.4 ) ) );
_4q1q3 = cats( strip(put( round(q1 ,0.0001 ) ,  8.4 ) ) ,  "~" ,strip(put( round(q3 ,  0.0001  )  ,  8.4 ) )   );
%end;
keep &grpvarn.  _1nnmiss  _2meansd  _3median   _4q1q3   _5minmax;
proc sort; by &grpvarn.;
run;  


* _3.3 transpose;
proc transpose data=_combdt  out=_trans1;
var   _1nnmiss  _2meansd  _3median   _4q1q3   _5minmax;
by &grpvarn.;
proc sort; by _NAME_  ;
run;

proc transpose data=_trans1  out=_trans2 prefix= _grp;
id &grpvarn.;
var  col1;
by _NAME_ ;
run;

*_3.4 rename column;
    proc contents data=_trans2 out=_info   noprint;

    proc sort;
        by varnum;
    run;

    proc sql   noprint;
        select count(distinct NAME) ,  NAME into: varn, :vnam1 -:vnam99 from _info;
    quit;

    data _&dtout.;
        set _trans2;
        %do ii = 1 %to &varn.;
            %let jj = %eval(&ii. - 1);
            rename &&vnam&ii. = C&jj.;
        %end;
    run;

* _3.5 label ;
    data _row1;
        length C0 $200.;
        C0 = cats("&label.");
    run;

    data _&dtout._;
        set _row1 
            _&dtout.;
    if c0 = "_1nnmiss" then C0 = "n(nmisssing)";
    if c0 = "_2meansd" then C0 = "Mean(SD)";
    if c0 = "_3median" then C0 = "Median";
    if c0 = "_4q1q3" then C0 = "Q1~Q3";
    if c0 = "_5minmax" then C0 = "Min~Max";
    run;

* _3.6 append ;
%if %sysfunc(upcase(&append.)) = %str(Y) and  %sysfunc(exist(&dtout., data) )  %then %do;

data &dtout.;
set %if %sysfunc(exist(&dtout., data) ) %then &dtout.;  _&dtout._ ;
run;
%end;
%else %do;
%put WARNING: 未追加数据集;

data &dtout.;
set  _&dtout._ ;
run;

%end;

*  _4 output data ;
*  _4.1 output ;
data &libout..&dtout.;
set &dtout.;
run;

*  _4.2 remove temp datasets ;
proc datasets lib=work;
delete _:;
run;

%mend;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容