SAS编程-Figure(GTL):分组线图绘制(分组绘图通讲)

欢迎关注,SAS茶谈!

最近使用GTL出了一批线图,基于此,再次梳理了一遍SAS中的GTL语言分组绘图的过程,内容涵盖了大部分GTL出图的设置操作,希望对读者的日常SAS出图工作有所帮助。

GTL的全称是Graph Template Language,是一个建立图形模板,并引用模板进行出图的过程。SAS中的出图工具还有SG系列过程步,与SG过程步相比,GTL在复杂布局和组合绘图方面更具优势和灵活性

0. 分组绘图的2种实现方式

关于分组,我习惯给2个称呼,区组分组分析分组 。通常,区组分组是指治疗分组变量,分析分组通常指其他分组变量。这两个称呼没有特别明显的划分,可以粗略类比Means过程步中,by语句和class语句。

这篇文章的分组绘图,指的是区组分组,即试验治疗分组

分组绘图常规的实现方式有两种:

  1. 为n个试验分组单独新建n个分析变量, 对每一个分析变量进行图形语句输出;
  2. 使用出图语句中自带的分组选项group=

对于阅读这篇文章的读者,推荐使用第2种方法进行分组绘图。后者程序的简洁性和易读性要大大超过前者!

部分使用第1种方法的人,是为实现分组图形的“错位”,这样操作,需要为每一个试验分组重新单独设置x轴变量值,过程会很繁琐。

第2种方法group=选项,有对应具体选项实现组间图形的“错位”,非常方便(groupdisplay=cluster clusterwidth=xx

这里插入一段往事,我工作中第一次输出的Figure非常复杂,加之时间紧、任务急,当时整个人有些焦虑。于是,找了一个印度同事类似的Figure代码,想着照葫芦画瓢出一下。代码使用的是第1种方法,当时的区组分组不止3个,分析分组也不止1个,参考的代码结构划分又及其混乱......那张Figure输出过程非常痛苦,印度同事在我心里留下了很不好的印象...

当然,从目的性角度看,不管使用哪种方法,只要能实现输出都是可以的。但是,代码思路、结构最好做到清晰,保证易读性,方便后续的维护、更新与借鉴

1. SAS绘图概览

临床试验的Figure输出,大体会涉及这5个方面内容:

  • 图形的选择以及参数设置
  • 坐标轴的设置
  • Legend的处理
  • 其他显示内容
  • 分组属性的设置

出图的第一步,是知晓自己所出的图形。对应的,需要认识用于出图的变量属性。

这次演示的图形为Mean Plot,用于查看不同组数据的均值是否有变化。y轴通常为为分组的均值,x轴通常为分析分组的各个类别。

Mean Plot

Mean Plot一般由散点图和折线图构成,散点图会配上误差线(Error Bar)。常见的误差线有95%的可信区间、分位数Q1Q3、Mean±SD等。若图中没有明确的误差线说明,需要跟统计师进行确认。这里我以分位数Q1Q3进行演示。

2. 生成演示数据

为了方便演示,我制作了一个模拟数据集。400位受试者分成3组(trt01an = 1, 2, 3),经历4个访视(avisitn = 0, 1, 2, 3)。对于受试者的访视检测值,给予一定的趋势变化。

***Get data for analysis;

**1. Data for BigN; 
data adsl(drop=ia);
    do ia = 1 to 400;
        usubjid = put(ia, z3.);
        trt01an = 1 + (ia>140) + (ia>270);  
        
        *Var for count;
        flag = 1;
        output;
    end;
run;

**2. Raw data for figure output;
data adb;
    set adsl;

    do avisitn = 0 to 3;
        if trt01an = 1 then aval = rand('normal',  77, 77) + rand('normal',  20, 20)*avisitn*rand('uniform',1,2);
        if trt01an = 2 then aval = rand('normal',  77, 77) + rand('normal',  10, 10)*avisitn*rand('uniform',1,2);
        if trt01an = 3 then aval = rand('normal',  77, 77) - rand('normal',  10, 10)*avisitn*rand('uniform',1,2);
        output;
    end;
run;

模拟数据中,正态分布的标准差给的很大,这是为了使误差线显示明显。

ADB

3. 统计量的计算

在出图之前,需要计算输出统计量。从图形实例看,我们需要各试验分组人数(N),各访视节点参与检测的人数(n),以及每个区组分组、每个分析分组的检测值均值(mean)以及对应的分位数(Q1、Q3)。下面介绍各个统计量的计算。

3.1 Format设置

一般来讲,如果一个试验分组人数为0,该试验分组的线图是不需要输出的。而对于部分访视值缺失的情况,小n是需要补0的。输出完整的分组计数,我使用的Means过程步中的preloadfmt选项,需要提前设置分组变量的Format,具体介绍参考:SAS编程:频数汇总时如何处理分析分组种类不全的情况?

***Create format for group var;
proc format;
    value trt01an
        1 = 1
        2 = 2
        3 = 3
    ;

    value avisitn
        0 = 0
        1 = 1
        2 = 2
        3 = 3
    ;
run;
3.2. 计算BigN

Legend含有BigN的信息,先计算bign,然后构建显示Legend内容的Format。为方便引用,将BigN数值保存到宏变量中,如果对数据不熟悉,可以生成一个N_check数据集,便于查看BigN宏变量的内容。

***Calculate statistics for figure output;

**1. Derive N in legend;
proc means data = adsl nway completetypes;
    format trt01an trt01an.;
    class trt01an/ preloadfmt order =  data;

    var flag;
    output n = bign out = bign;
run;

data _null_;
    set bign;
    call symputx("N_"||strip(put(trt01an, best.)), strip(put(bign, best.)) );
run;

data N_check;
    set sashelp.vmacro;
    where index(name, "N_");
run;

*Create format for legend display;
proc format;
    value trt_dis(notsorted)
        1 = "Placebob (N = &N_1.)"
        2 = "Treatment A (N = &N_2.)"
        3 = "Treetment B (N = &N_3.)"
    ;
run;
3.2 计算小n及分位数

小n的计算需要输出完整的分析分组的信息(avisitn,class语句),对于缺失的试验分组不需要显示(trt01an,by语句)。严格来讲,后面输出figure需要判断是否有试验分组缺失,进而调整输出语句,对于模拟数据我就省事不判断了。

输出统计量的精度最好固定,这样避免精度对QC结果的影响。

**2. Derive mean and q1q3;

proc means data = adb nway completetypes;
    by trt01an;

    format avisitn avisitn.;
    class avisitn / preloadfmt order = data;

    var aval;
    output out = stat1 n = n mean=mean q1=q1 q3=q3 ;
run;

data stat;
    length legend $30;
    set stat1;
    
    legend = put(trt01an, trt_dis.);
    mean = round(mean, 0.001);
    q1 = round(q1, 0.001);
    q3 = round(q3, 0.001);

    drop _:;
run;

统计量计算完毕后,下面正式使用GTL输出Figure。

4. GTL语言Figure的输出

演示过程为常规的编程流程。

4.1 模板的建立与引用

先用最基础的Plot语句将图形的主体部分输出来:

**1. Create template for figure;
proc template;
    define statgraph plot;
        
        begingraph;
            layout overlay;
                scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1  yerrorupper=q3 group = trt01an ;
                seriesplot y=mean x=avisitn / name="series" group=trt01an ;
            endlayout;
        endgraph;

    end;
run;

**2. render the template;
proc sgrender data=stat template=plot;
    format trt01an trt_dis.;
run;

默认情况下,组与组的图形是重合在一起的,即默认groupdisplay=stack在使用cluster选项值且调试好合适的组间距(groupdisplay=cluster clusterwidth=0.2),就可以实现错位展示。输出结果如下:

4.2 坐标轴的内容设置

坐标轴内容的设置一般有3个:

  1. Label的显示
  2. 首尾留白区域
  3. 刻度设置以及对应内容显示

坐标轴的Label通过label=选项设置,一般格式选默认,若需要格式设置,在labelattrs=( )中设置,具体属性参考SAS官方文档。

上一张输出的x轴前后留白区域看起来有些大,可以通过offsetmin=offsetmax=选项进行设置。调试的原则,是怎么美观怎么调。有一点要注意,留白区域过大可能造成坐标轴刻度内容显示不全

坐标轴的刻度设置通过linearopts=()选项设置。坐标轴刻度分为两类,一类是连续型刻度,一类是离散型刻度。两者对应的选项分别为tickvaluesequence=(start= end= increment=)tickvaluelist=( )。一般也会对应设置好,坐标刻度的显示范围,viewmin= viewmax=。离散型坐标轴刻度对应的说明内容,可以通过选项tickdisplaylist=( )设置。

演示代码如下:

**1. Create template for figure;
proc template;
    define statgraph plot;
        
        begingraph;
            layout overlay/
                yaxisopts=(
                    label="Actual Value"  offsetmax = 0.2 
                    linearopts=(viewmin=-50  viewmax=300  tickvaluesequence=(start=-50  end=300  increment=50))
                )
                xaxisopts=(
                    label="Analysis Visit"  offsetmin = 0.05 offsetmax = 0.06
                    linearopts=(viewmin=0  viewmax=3  tickvaluelist=(0 1 2 3 ) tickdisplaylist=("Baseline" "Visit 1" "Visit 2" "Visit 3") )
                );

                scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1  yerrorupper=q3 group = trt01an groupdisplay=cluster  clusterwidth=0.2;
                seriesplot y=mean x=avisitn / name="series" group=trt01an groupdisplay=cluster clusterwidth=0.2;

            endlayout;
        endgraph;

    end;
run;

**2. render the template;
proc sgrender data=stat template=plot;
    format trt01an trt_dis.;
run;

输出内容如下:

坐标轴内容设置完毕,基本达到美观效果。坐标轴的设置除了以上介绍的内容外,还有一些其他选项,例如添加小坐标、坐标翻转等。具体细节参考GTL官方文档(SAS Help Center: SAS 9.4 Graph Template Language: Reference, Fifth Edition),这里就不详细介绍了。

4.3 Legend的处理

上一张输出的y轴上方留有不小的空白位置,这一片区域是预留给Legend的位置。Legend,图例说明,一般用于说明图形属性与分组的对应关系

因为这张Figure是散点图和折线图的结合,图例的设置也需要将“点与线”相结合。先看一下常规使用的Legend语句的效果如何?

**1. Create template for figure;
proc template;
    define statgraph plot;
        
        begingraph;
            layout overlay/
                yaxisopts=(
                    label="Actual Value"  offsetmax = 0.2 
                    linearopts=(viewmin=-50  viewmax=300  tickvaluesequence=(start=-50  end=300  increment=50))
                )
                xaxisopts=(
                    label="Analysis Visit"  offsetmin = 0.05 offsetmax = 0.06
                    linearopts=(viewmin=0  viewmax=3  tickvaluelist=(0 1 2 3 ) tickdisplaylist=("Baseline" "Visit 1" "Visit 2" "Visit 3") )
                );

                scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1  yerrorupper=q3 group = trt01an groupdisplay=cluster  clusterwidth=0.2;
                seriesplot y=mean x=avisitn / name="series" group=trt01an groupdisplay=cluster clusterwidth=0.2;

                discretelegend "scatter" "series"/ title="" location=inside halign=right valign=top across=1 border=false valueattrs=(size=7pt);

            endlayout;
        endgraph;

    end;
run;

**2. render the template;
proc sgrender data=stat template=plot;
    format trt01an trt_dis.;
run;

结果如下:

可以看到点图和线图的图例是分开显示的,对于这张图形来讲,是不合适的。点与线的Legend需要结合起来,通过mergedlegend语句实现。

mergedlegend "scatter" "series"/ title="" location=inside halign=right valign=top across=1 border=false valueattrs=(size=7pt);

输出图形如下:

有关GTL Legend的其他内容,可以参考文章:

4.4 其他内容的显示

与文章开头展示的样图相比,目前图形还缺少以下内容:Title,footnotes,以及分组小n的显示。

4.4.1 Title

图形的Title直接使用entrytitle语句,通常不需要修改默认属性。

*titles;
entrytitle "Mean Plot";
4.4.2 Footnotes

图形的Footnote使用entryfootnote语句,通常是需要设置文字内容属性的。为避免与图形内容太近,通常还会添加空行(entryfootnote " ";)。

entryfootnote " ";
entryfootnote halign=left "Program: gtl_mean_plot.sas" /pad=(top=10px) textattrs=(size=9pt style=italic);
entryfootnote halign=left "Output: Mean_plot.rtf  (Date Generated: &sysdate &systime)"/ textattrs=(size=9pt style=italic);

正常项目中,一些footnotes有可能保存在宏变量中,这时候也需要将其添加到图形中,通过宏循环进行实现。(这里就不新建完整宏程序进行演示了)

*footnotes;
entryfootnote " ";

%if &num_footnots > 0 %then %do ia = 1 %to &num_footnotes;
    entryfootnote halign=left "&&foot&ia."/textattrs=(size=9pt);
%end;

entryfootnote halign=left "Program: gtl_mean_plot.sas" /pad=(top=10px) textattrs=(size=9pt style=italic);
entryfootnote halign=left "Output: Mean_plot.rtf  (Date Generated: &sysdate &systime)"/ textattrs=(size=9pt style=italic);
4.4.3 小n的显示

小n区域的设置,首先通过innermargin; endinnermargin;模块,开辟一个嵌套区域进行显示。具体文字的罗列,可以通过blockplot语句或axistable语句实现。不过,两者实现的方式有一些区别。

blockplot语句使用分组处理时,每一组别的文字属性无法单独设置。如果想要每一组的内容属性不同,需要新建新的分析变量(第1种方法)进行拆分处理。同时,首行的文字内容(Number of Subjects (n):)也需要新建一个变量进行保存,使用一个单独blockplot语句进行输出。暂不接介绍blockplot语句的实现过程。

axistable语句有单独的颜色分组控制选项colorgroup=,同时,首行文字也可以通过选项headerlabel=进行输出。

演示代码如下:

**1. Create template for figure;

proc template;
    define statgraph plot;
        
        begingraph;
            layout overlay/
                yaxisopts=(
                    griddisplay=off  label="Actual Value"  offsetmax = 0.2
                    linearopts=(viewmin=-50  viewmax=300  tickvaluesequence=(start=-50  end=300  increment=50))
                )
                xaxisopts=(
                    griddisplay=off  label="Analysis Visit"  offsetmin = 0.05 offsetmax = 0.06
                    linearopts=(viewmin=0  viewmax=3  tickvaluelist=(0 1 2 3 ) tickdisplaylist=("Baseline" "Visit 1" "Visit 2" "Visit 3") )
                );

                scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1  yerrorupper=q3 group = trt01an  groupdisplay=cluster clusterwidth=0.1;
                seriesplot y=mean x=avisitn / name="series" group=trt01an  groupdisplay=cluster clusterwidth=0.1;

                mergedlegend "scatter" "series"/ title="" location=inside halign=right valign=top across=1 border=false valueattrs=(size=7pt);

                *small n;
                innermargin /align=bottom;
                    axistable x=avisitn value=n /  name="division"  headerlabel="Number of Subjects (n):" headerlabelattrs=GraphLabelText 
                        valueattrs=(size=9pt ) colorgroup=trt01an class=trt01an;
                endinnermargin;
            endlayout;

            *titles;
            entrytitle "Mean Plot";

            *footnotes;
            entryfootnote " ";

            entryfootnote halign=left "Program: gtl_mean_plot.sas" /pad=(top=10px) textattrs=(size=9pt style=italic);
            entryfootnote halign=left "Output: Mean_plot.rtf  (Date Generated: &sysdate &systime)"/ textattrs=(size=9pt style=italic);
        endgraph;
    end;
run;

**2. render the template;
proc sgrender data=stat template=plot;
    format trt01an trt_dis.;
run;

这样输出,有一个小问题,小n左侧显示文字信息沿用了trt01an的format,与Legend内容相同,想显得冗余。

如果小n左侧信息想要显示其他内容,可以新建一个变量作为分组变量,其值等于trt01an(等于其他有序数值也行),分组变量值的Label设置成想要显示的内容,axistable语句中的class=选项值替换为新变量。

proc format;
    value nclass
        1 = "1:"
        2 = "2:"
        3 = "3:"
    ;
run;

data stat_dis;
    set stat;
    nclass = trt01an;
run;

proc sgrender data=stat_dis template=plot;
    format trt01an trt_dis.;
    format nclass nclass.;
run;

输出内容如下,小n左侧信息得到更新:

4.5 分组属性的设置

以上输出的点、线、数值的属性都是由SAS系统自动分配,直观上看起来并不美观,那如何为每个不同组别的输出内容进行属性设置呢?

这需要通过DiscreteAttrmapDiscreteAttrvar语句来实现

DiscreteAttrmap语句是用于创建一个属性映射,该属性映射将图形属性离散值(分组类别值)匹配,即属性映射可以与图中的分类变量相关联。这里需要注意,如果分组变量有Format,对应匹配值是具体的Format值

可以关联的属性有以下4类:

  • Fillattrs
  • Lineattrs
  • Markerattrs
  • Textattrs

DiscreteAttrvar语句是用来在已定义的属性映射和包含对应分类值的分类变量之间创建一个关联。attrvar=选项指定属性映射的关联名称,var=选项指定数据集中的分组变量,attrmap=指定已经定义好的属性映射名称。

这里附上一些属性设置内容,参考:SAS Help Center: Attributes Available for the Attribute Options

符号的设置:

线型的设置:

颜色的设置:

可以运行以下程序查看SAS支持的颜色命名:

proc registry list
    startat="COLORNAMES";
run;

常用颜色如下图:

属性映射的定义与关联是不在layout overlay; endlayout模块中的,具体的代码实现如下:

**1 Create template for figure;
proc template;
    define statgraph plot;
        
        begingraph;
            *Attributes for each group var level;
            discreteattrmap name = "symbols" / ignorecase = true;
                value "Placebob (N = &N_1.)"  / markerattrs=(color = blue symbol = circle);
                value "Treatment A (N = &N_2.)"  / markerattrs=(color = red symbol = triangle);
                value "Treetment B (N = &N_3.)"  / markerattrs=(color = green symbol = x);
            enddiscreteattrmap;

            discreteattrmap name = "lines" / ignorecase = true;
                value "Placebob (N = &N_1.)"  / lineattrs=(color = blue pattern = 1);
                value "Treatment A (N = &N_2.)"  / lineattrs=(color = red pattern = 4);
                value "Treetment B (N = &N_3.)"  / lineattrs=(color = green pattern = 14);
            enddiscreteattrmap;

            discreteattrmap name = "values" / ignorecase = true;
                value "1:"  / textattrs=(color = blue);
                value "2:"  / textattrs=(color = red );
                value "3:"  / textattrs=(color = green);
            enddiscreteattrmap;

            *Associate the attribute map with group var;
            discreteattrvar  attrvar = groupmarkers var=trt01an attrmap="symbols";
            discreteattrvar  attrvar = grouplines var=trt01an attrmap="lines";
            discreteattrvar  attrvar = groupvalues var=nclass attrmap="values";

            layout overlay/
                yaxisopts=(
                    griddisplay=off  label="Actual Value"  offsetmax = 0.2
                    linearopts=(viewmin=-50  viewmax=300  tickvaluesequence=(start=-50  end=300  increment=50))
                )
                xaxisopts=(
                    griddisplay=off  label="Analysis Visit"  offsetmin = 0.05 offsetmax = 0.06
                    linearopts=(viewmin=0  viewmax=3  tickvaluelist=(0 1 2 3 ) tickdisplaylist=("Baseline" "Visit 1" "Visit 2" "Visit 3") )
                );

                scatterplot y=mean x=avisitn/ name="scatter" yerrorlower=q1  yerrorupper=q3 group = groupmarkers  groupdisplay=cluster clusterwidth=0.1;
                seriesplot y=mean x=avisitn / name="series" group=grouplines  groupdisplay=cluster clusterwidth=0.1;

                mergedlegend "scatter" "series"/ title="" location=inside halign=right valign=top across=1 border=false valueattrs=(size=7pt);

                *small n;
                innermargin /align=bottom;
                    axistable x=avisitn value=n /  name="division"  headerlabel="Number of Subjects (n):" headerlabelattrs=GraphLabelText 
                        valueattrs=(size=9pt ) colorgroup=groupvalues class=groupvalues;
                endinnermargin;
            endlayout;

            *titles;
            entrytitle "Mean Plot";

            *footnotes;
            entryfootnote " ";

            entryfootnote halign=left "Program: gtl_mean_plot.sas" /pad=(top=10px) textattrs=(size=9pt style=italic);
            entryfootnote halign=left "Output: Mean_plot.rtf  (Date Generated: &sysdate &systime)"/ textattrs=(size=9pt style=italic);

        endgraph;
    end;
run;

**2. render the template;
proc sgrender data=stat_dis template=plot;
    format trt01an trt_dis.;
    format nclass nclass.;
run;

代码运行,输出完毕:

总结

这篇文章以Mean Plot为例,梳理了GTL输出分组绘图的过程。最为关键的一步是,选择group=选项进行分组输出,组与组的输出错位通过groupdisplay=cluster实现。后续介绍了,GTL中坐标轴的设置Legend的处理其他显示内容分组属性的设置,涵盖了GTL出图的大部分设置内容。

感谢阅读, 欢迎关注:SAS茶谈!
若有疑问,欢迎评论交流!

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

推荐阅读更多精彩内容