Monatsarchiv für July 2011

 
 

Retrieve blogs using SAS

Recently I posted a frequency analysis on Rick Wicklin’s popular SAS/IML blog. Sanjay Matange also produced a nice heatmap on Rick’s blogging history using the summary data I published. Here just release the ideas and SAS codes to get data from Rick’s blog dynamically. You may modify the codes slightly to obtain data from all other SAS in-house blogs (http://blogs.sas.com/index.php) since they share the same template. For other blogs, you should research the web pages accordingly to get the best suitable methods and this post can also serve as an example.

First step: define the scope

For my purpose, I only need the titles and publish dates of Rick’s posts. It is so called the “metadata” of the blog. I do not need all the post contents. By the way, if all information needed, you can use a blog backup tool, or write codes to retrieve all the pages of http://blogs.sas.com/iml at the maximum depth, or simply, you can write to Rick and say: hey Rick, could you please send me all the contents of your blog? And Rick may go to the management console of his own blog, export all the contents to an XML file and get back to you.

Second step: analyze the web pages

Browse to the right panel of Rick’s blog, in the ARCHIVES frame, click “Older:

clip_image002

And you get

clip_image003

This page just gives a big picture of Rick’s blog (ARCHIVE section is always a good place to get such metadata, for example, archives for my blog). But we need more. Click “view topics” for example of September, 2010:

clip_image004

This page is exactly what we want with titles and dates. Open an editor to write codes immediately to read all the information in this page?—wait. Currently this blog has posts across 11 months and you can expect the increase. You should design a dynamic method to read all the topics pages: Sep 2010, Oct 2010, … and, today().

Return to the archives page. RCM (right click your mouse) and select “View page source” if you use Google Chrome web browser (“View Source” in IE; “View Page Source” in Firefox) and you get all the HTML scripts (Note: you DO not need any knowledge of HTML to understand this post). Copy and paste them into a text editor supporting HTML syntax highlighting (such as Notepad++). Search all instances of “view topics” we mentioned before:

clip_image006

We are lucky. They are 11 instances of “view topics” accompanying with 11 hyperlinks for the currently 11 months’ archives of Rick’s blog. We can read such 11 hyperlinks to a macro array for dynamic retrieval.

Then we return to the single topics page, for example of September, 2010. Review the HTML source file. Search for “posted_by_date” and we get 14 instances which is same as the number of posts in September 2010:

clip_image008

We should also need to locate all the instances of titles. Search “/iml/index.php?/archives/” and we get 17 responses:

clip_image010

We see 3 instances at end of the finding results don’t contain any titles. You can check other pages to confirm such pattern. Yes we can use regular expressions to parse the HTML pages to locate more exactly for the titles. But for a quick job and due to the relative simple HTML pages, some basic SAS character functions are enough for our purpose. In the following codes, limited regular expressions are used only to remove HTML tags such as “<a href=”.

After such explorative search of HTML scripts, we can get the basic idea where can we find the interested information. Then we begin to coding work.

Third step: Coding at last!

For our purpose, we should first read the archive page to get all the topics links to a macro array, then read the all the topics pages dynamically. Finally, we should also add the all the calendar dates with holidays. Some friends may find that they met piece of the following codes before. Yes, such codes just assembled some skills what I learned from Art Carpenter, Richard DeVenezia, Jian Dai and lots of programmers before!

3-1: read archive page

%let URL=http://blogs.sas.com/iml/index.php?/archive;

filename archive URL "&URL";

data archive;
    length text $1024;
    infile archive lrecl=1024;
    input text $;
    text= _infile_;
    if index(text, ">view topics<") then output;
run;

data  archive1;
    set archive;
    summary=scan(text,4,’"’);
run;

3-2: read all topics pages

data _null_;
    set  archive1 end=eof;
    I+1;
    II=left(put(I,2.));
    call symputx(‘summary’||II,summary);
    if eof then call symputx(‘total’,II);
run;

%macro readit;
    %do i=1 %to &total;
        filename f&i URL "&&summary&i";   

        data f&i;
            length text $1024;
            infile f&i lrecl=1024;
            input text $;
            text= _infile_;
            if index(text, "/iml/index.php?/archives/") or index(text, "posted_by_date") then output;
        run;

    /*remove HTML tags;*/
    data ff&i;
        set f&i; 
        prx=prxparse("s/<.*?>//");
          call prxchange(prx,99,text);
        drop prx;

        flag=ifn(mod(_n_,2),1,2);
        grpn=&i;
        if index(text,"201") and length(text)<10 then delete;
/*be carefull! hard coding;*/
        seq=_n_;
     run;

     /*transpose data;*/
     data fff&i;
         set ff&i;
        by grpn seq flag;

        retain title;
        if first.flag then title=lag(text);
        if flag=1 then delete;
     run;

    %end;

%mend readit;
%readit

%macro getall;
    data Rick;
        set %do i=1 %to &total; fff&i %end; ;
        seq=seq/2;   
        drop flag;
    run;   
%mend getall;
%getall

data rick2;
    set rick;

    datetime=scan(text,2,",");     
    year=scan(text,2,".");       
    month=scan(scan(text,2,","),1);
    day=scan(scan(text,2,","),2);     
    week=scan(text,6);            
    dt=compress(catx("",day,substr(month,1,3),year));  
    worddat=input(dt,date9.);                        
    format worddat  ddmmyy10.;                        

    m=scan(put(worddat,ddmmyy10.),2);             
    my=compress(catx("",year,m));                     
run;

proc sort ;
    by  worddat descending seq ;
run;


Click to read more…

SAS Bloggers In Action(1): Rick Wicklin, SAS/IML and “Color Revolution”

It is well known that the French writer, author of The Three Musketeer, Alexandre Dumas, wrote his master piece of work in different colored papers according to literary genre:

non-fiction on  rose,

fiction on blue,

poetry on yellow

The SAS blog writer, author of Statistical Programming with SAS/IML Software, Rick Wicklin of SAS Institute,  also leads a strong “color revolution” in SAS blog community:

JohariWindowIn an interesting personal statement, Blogging, Programming, and Johari Windows, Rick summarizes his rich and colorful blogging rhythms according to the above Johari window:  

  • Mondays, writes introductory notes (corresponding to the upper right quadrant of Johari window). 
  • Wednesdays, experimental articles on sampling, simulation and other statistical programming topics(lower left quadrant).
  • Fridays, on explorative analysis of data (upper left quadrant).

So what about the lower right quadrant? Rick rediscovers and exposes what he once knew. Just suppose that, Rick picks up some codes he wrote before (ten years ago maybe) with big surprise: oh, who on earth wrote such damned clever beautiful codes? He or she must be in his/her aggressive youth. –then Rick wrote them all in blog.

Here I produced a summary table for Rick’s blogging activities (numbers per month per weekday; before July 16, 2011 Beijing time; next following post would introduce how to use SAS to analyze data from website such as Rick’s blog):

Rick 

Key findings:

  • Rick is really a frequent and productive blogger with averagely 0.5 posts per day!
  • Rick DOES keep his words. Most of the posts are published in Friday, Wednesday and Monday(44, 44, 42 posts respectively).
  • None posted in Saturdays and Sunday.

Rick began his writing since September  3. 2010, Friday.  Up to July 15. 2011, Friday, there are 48 Fridays, 46 Wednesdays and 46 Mondays.  Only 10 colored weekdays (4 Fridays, 2 Wednesdays and 4 Mondays) passed with no posts and most all them are due to national holidays:

06/09/2010 , Monday       : Labor Day
24/11/2010 , Wednesday: round Thanksgiving Day
26/11/2010 , Friday         : round Thanksgiving Day
22/12/2010 , Wednesday
24/12/2010 , Friday         : Christmas Day
27/12/2010 , Monday
31/12/2010 , Friday         : New Year’s Day
30/05/2011 , Monday      : Memorial Day
10/06/2011 , Friday
04/07/2011 , Monday      : Independence Day

At least in 4 holidays (most in Monday), Rick was also active in writing:

11/10/2011, Monday, Columbus Day: How Do You Reshape a Matrix?
11/11/2010, Thursday, Veterans Day: It’s Here!
17/01/2011, Monday, Birthday of Martin Luther King, Jr.: On the Flip Side: Exchanging Rows and Columns
21/02/2011, Monday, Washington’s Birthday: How to Build a Vector from Expressions

Amazing Rick keeps a fixed writing pattern and in next following post, detailed analysis and SAS codes will be presented so you can also keep eyes on the metadata of your favorite bloggers’ writing and may rise a question like:

Hey Rick, what’s up in Jun 10, 2011, Friday?

Tango Haiku

Happy weekend and Haiku again!

My finger hurts,

I can not dance

Tango in Bastille.

So… any ideas?

… …

… … …

OK. I am a terrible Haiku writer. The sentences are not self explained without  /*commentaries*/ . So the story behind the scene…

One day my remote workstation was very very slow due to some network issues. But I still needed to write and run SAS codes in the server which is located in France. It really hurt my fingers from the ergonomic point of view.

“Tango” was my project code. But why Bastille? oh, that day happened to be round the Bastille Day in France. The performance of the remote server that time was just as terrible as the Bastille I want to break so I could do my project work more smoothly!

—————————–

Note that after my completing this Tango Haiku, I did a Google search and found that there is really a French book called Bastille tango!

Tango

A SAS Implementation of Confidence Intervals for Single Proportion: Eleven Methods

The following two papers by Professor Robert Newcombe, in my limit observation, are the most frequently cited papers in the industry for CI calculation:

This post serves as an implementation using SAS accompany with the first paper on confidence intervals for single proportion(method 1-7). Additional 4 methods also provided:

1.  Simple asymptotic, Without CC                       
2.  Simple asymptotic, With CC                          
3.  Score method, Without CC                            
4.  Score method, With CC                               
5.  Binomial-based, ‘Exact’                             
6.  Binomial-based, Mid-p                               
7.  Likelihood-based                                    
8.  Jeffreys                                            
9.  Agresti-Coull,z^2/2 successes                       
10. Agresti-Coull,2 successes and 2 fail                
11. Logit

The codes are available at

http://jiangtanghu.com/docs/en/CI_Single_Proportion.sas 

It is a purely SAS/Base(data step and SQL) approach. I prefer data steps because it can be seamlessly incorporate into production work which looks like:

/*r is the observed number of events/responders in n observations;*/

%macro _Wald(r,n,alpha=0.05);

      p=&r/&n;
      z = probit (1-&alpha/2);
      sd=sqrt(&n*p*(1-p)); *Standard Deviation ;
      se=sd/&n;            *standard error;
      me=z*se;             *margin of error;
      p_CI_low = p-me;
      p_CI_up  = p+me;
     p_CI=compress(catx("","[",put(round(p_CI_low,0.0001),6.4),",",put(round(p_CI_up,0.0001),6.4),"]"));
%mend _Wald;

data test;
      input r n;
     %_Wald(r,n);
      keep r n p p_CI;
datalines;
81 263
15 148
0 20
1 29
29 29
;  
run;

proc print data=test;run;

But when simulation required (in methods 6-7), some SQL clauses pop up. Admit that It makes the codes less elegant. In this situation, matrix operation language such as SAS/IML or R will do a better job.

Note that in SAS 9.2, PROC FREQ can produce the following five intervals(method 1, 3, 9 ,8, 5):

Wald (also with method 2)                  
Wilson                
Agresti-Coull         
Jeffreys              
Clopper-Pearson (Exact)

For the five intervals in SAS 9.2, a good reference is Confidence Intervals for the Binomial Proportion with Zero Frequency by Xiaomin He and Shwu-Jen Wu:

www.pharmasug.org/download/papers/SP10.pdf

Also, an equivalent R version of the eleven methods by Abteilung Kriminologie is also available at

http://www2.jura.uni-hamburg.de/instkrim/kriminologie/Mitarbeiter/Enzmann/Software/prop.CI.r

Such R repository is also a good resource for SAS users. I almost “translate” R implementations directly to SAS for this post.

Dive into CDISC Express (5): Generate and Validate SDTM domains and define.xml

Dive into CDISC Express (1): Introductory

Dive into CDISC Express (2): Create a New Study

Dive into CDISC Express (3): Navigate mapping file

Dive into CDISC Express (4): Data manipulation techniques

A more friendly PDF version of these all CDISC Express series is also available in

http://jiangtanghu.com/docs/en/CDISCExpress.pdf

The following tasks, such as generating SDTM domains and define.xml, need just some clicking button work in CDISC Express using a well designed mapping file. Few words needed due to the software.


Click to read more…

Dive into CDISC Express (4): Data manipulation techniques

Dive into CDISC Express (1): Introductory

Dive into CDISC Express (2): Create a New Study

Dive into CDISC Express (3): Navigate mapping file

4.3 Data manipulation techniques in CDISC Express

CDISC Express supplies relative rich sets of data manipulation techniques assembling with SAS languages used for data mapping. Following is a not limited listing and I will keep it updated.

4.3.1 Reference one dataset

A raw dataset name appear in “Dataset” column indicate a “set” operation in SAS.

All dataset options can be used when referencing a dataset, such as

siteinv(drop=invcode)

siteinv(rename=(invcode=inv))

siteinv(where=(invcode ne “”))

You can also reference an external dataset. You should incorporate the external file in spreadsheet with name beginning with an underscore, “_”, and “_visits” in this case:

clip_image001

Then you can use it in any domains needed, e.g., TV domain:

clip_image003

There is a macro %cpd_importlist used to import the external dataset, “_visits”. Again, this macro roots in C:Program FilesCDISC Expressmacrosfunction_library.

Using a macro call to re-sharp or modify an input dataset offers great flexibility referencing data. We will also discuss the benefits later on.

4.3.2 Assignment

You can assign a number, string and a dataset variable with any valid SAS functions to a SDTM domain variable in “Expression” column.

Sometimes a temporary variable needed for later calculation. You can produce such temporary variable in “Dataset” column with an assignment in the “Expression” column just similar with any other domain variables. Two differences: first, such temporary variables named begin with an asterisk, “*”; second, all temporary variables will not be included in the final domain. Once created, such temporary variables can be used for any other expressions.

clip_image005

There are three special symbols used in “Dataset” column of CDISC Express. Asterisk, “*” indicates a temporary variable, while other two are

Tilde, “~” : indicate a variable used for supplemental domain (SUPPQUAL).

Number sign, “#”: indicate a variable used for comments domain (CO).

Another symbol, at sign, “@”, used in “Expression” column, indicated referencing a variables produced before:

clip_image006

In this case, “AGEU” uses “AGE” as input, while “AGE” is calculated before. “@AGE” just indicates the dependency. In concept, it looks like the “calculated” option in SAS PROC SQL:

proc sql ;

select (AvgHigh – 32) * 5/9 as HighC ,

(AvgLow – 32) * 5/9 as LowC ,

(calculated HighC – calculated LowC)

as Range

from temps;

quit;

4.3.3 Match-merging

We already got a math-merging example before. If “all” appears as a dataset in the “Dataset” column, all the previous datasets should be merged first for later processing by the common key specified in “Merge Key” column. If no key assigned, patient ID is used by the system.

CDISC Express also supports two types of join, inner join and outer join (left, right, full) using data steps. The implementation has slightly difference with standard SQL, but the ideas are same.

We add a new column, “Join”, usually beside the “Merge Key” column.

clip_image008

There are two values for “Join”, “O” or “I” while “O” stands for “outer join” and “I”, “inner join”. A join indicator “I” equals a dataset option “in=” in action while “O” means no. Use the above as illustration, the corresponding SAS codes behind look like

data temp;

merge demog(in=a) siteinv(in=b);

by sitecode;

if b;

run;

This is so called “right outer join”. The combination of “I” and “O” in these two datasets can perform all the four types of join, one inner join and three outer join:

in 

As we could see, if no “Join” column specified, CDISC Express will perform inner join by default.

So far CDISC Express cannot support multiply merge keys. For example, the following file is illegal currently:

 

Dataset

Merge Key

arm   

siteid, grpno

armdescri

siteid, grpno

The developer Romain indicated that such enhancements would be raised to the next round of product road map and he also proposed a work around. To use multiple keys for merging, we can create a temporary variable holding such multiple keys as a concatenation then this temporary variable can be used as a single merging key.

4.3.4 Concatenating

Above we discussed lots about “merge” operation in CDISC Express. This section dedicated for “set” operation. We already know how to “set” one dataset for referencing, but how to “set” multiple datasets, i.e, “Concatenating”?

Symmetrically, an “all” appears in “Dataset” column indicating merging operation, an “all (stack)” indicates concatenating operation:

clip_image014

The above file can be also translated to SAS codes for better understanding:


Click to read more…

Dive into CDISC Express (3): Navigate mapping file

Dive into CDISC Express (1): Introductory

Dive into CDISC Express (2): Create a New Study

4. Step 2 of 6: Generate mapping file

Generating template (blank) mapping file only needs pieces of effort by submitting generate_mapping_template.sas. The toughest one is to fill it with mapping rules according to specified study.

4.1 Get the blank template mapping file (generate_mapping_template.sas)

To get the blank template mapping file, just fill the one line of macro call in generate_mapping_template.sas:

%createmapping(filespec=SDTM_Specs_3_1_1.xls, Dom=CM AE TV, req=YES, perm=YES, exp=YES);

Also, you can specify SDTM implementation version, 3.1.1 or 3.1.2. For domains (&Dom), DM, CO and SUPPQUAL will be created automatically; you should list others accordingly:

SDTM 3.1.1: SV CM EX AE DS MH DV EG IE LB PE QS SC VS TV TI XD SU XR XS XE TR (Total: 22)

SDTM 3.1.2: AE CE CM DA DS DV EG EX FA IE LB MB MH MS PC PE PP QS SC SE SU SV TA TE TI TS TV VS (Total: 28)

You should also choose the “CORE” variable (REQUIRED, PERMISSIBLE and EXPECTED) by triggering &req, &perm, and &exp to “YES” or “NO”. Note that

REQUIRED and EXPECTED variables must always be included (req=YES, exp=YES);

PERMISSIBLE variables included if needed (perm=YES or perm=NO)

Submit generate_mapping_template.sas and you can get a blank template mapping file tmpmapping.xls in C:Program FilesCDISC Expresstemp.

clip_image002

Copy it to C:Program FilesCDISC ExpressstudiesCLINCAPdocMapping file – working version for example used for study “CLINCAP” and then fill all the blank columns (it NEEDS efforts!).

If this mapping file passes the validation process, a final version named mapping.xls will be copied automatically to C:Program FilesCDISC ExpressstudiesCLINCAPdocMapping file – validated version for later processing.

Note that if you already have some validated mapping file for other studies, it would serve as a good start rather than using the blank template from the scratch.

4.2 Navigate mapping file

Let’s take a look at the “real” worked mapping file for a demo study first, in C:Program FilesCDISC Expressstudiesexample1docMapping file – working version.

The first sheet is a welcome dashboard:

clip_image004

Then StudyMetadata sheet, a XML metadata specification used to generate define.xml. you need only add some information in “Values” column:

clip_image006

The FORMAT sheet:

clip_image007

Such format structure is similar with the one we export the format from a format catalog using

proc format library=library cntlout=format_out;

run;

In most production environment, programmers get formats from clinical data management group. If the entire formats are assigned into proper libraries (work or library), you don’t need to export such formats into this spreadsheet. Of course in the format sheet, you can type some customized format.

A typical domain sheet (AT LAST!) that needs efforts and our understanding of the software, DM for example:

clip_image009

From the ‘Dataset’ column, three raw datasets from C:Program FilesCDISC Expressstudiesexample1source needed to map into DM domain, demog, siteinv and eligassess. Note that you can use any data step options such as drop=, rename=, where= for the input datasets.

At the last of ‘Dataset’ column, “all” indicates that all the previous datasets mentioned above should be merged together for final processing.

In the ‘Merge Key’ column, ‘sitecode’ is designed to datasets demog and siteinv which means demog and siteinv should be merged by the common key, ‘sitecode’.

As we mentioned, all the previous datasets should be merged at last. But there is no common key settled in the ‘Merge Key’ column. It is a common rule: if no key specified for merge, USUBJID is used by default.

The third column is ‘CDISC variable’, which list all the needed variables according to implementation version. An important note: you do not need to implement all the variables according to the order as they appear in the blank template mapping file. In the previous blank file, “AGE” in DM domain is ordered in Line 12, but in this working file, “AGE” is calculated in the second last order. The variable order of final DM domain will be as same as the blank one.

It makes sense in practice. For example, the sequential variable, e.g. AESEQ is ordered after USUBJID, but you can only get the sequential number when all other variables well done. So SEQ variables are always computed in the final stage in a working mapping file.

“Expression” column specify the mapping rule from raw datasets to SDTM domains. Assignments, expressions and macro calls (rooted in C:Program FilesCDISC Expressmacrosfunction_library) are allowed in this column and most of them are straightforward. We will discuss more in the following section.

Sum up, we can “translate” this mapping sheet to SAS codes for better understanding of CDISC Express architecture:


Click to read more…

Dive into CDISC Express (2): Create a New Study

Dive into CDISC Express (1): Introductory

3. Step 1 of 6: Create a new study (create_new_study.sas)

Open create_new_study.sas in C:Program FilesCDISC Expressprograms, you can see only one line of a macro call:

%addnewstudy(studyname=my new study);

Just assign a study name to the macro variable, &studyname, e.g, “CLINCAP”:

%addnewstudy(studyname= CLINCAP);

Submit the codes, you can find a folder named “CLINCAP” with the same structure as the two demo studies imbedded in this application(example1 and example2) in C:Program FilesCDISC Expressstudies, see(the left and right panels are folders and files before and after the execution of create_new_study.sas. The following the same):

new

Folder ‘doc’ is used to hold the mapping files;

Folder ‘log’ used to hold log files generated by following macro calls, such as generating SDTM domains;

Folder ‘results’ and its subfolder will hold all the outputs, such as define.xml, SAS transport file, validation reports and SDTM datasets;

Folder ‘source’ holds all the clinical raw data used as inputs for SDTM domains;

Folder ‘tempdata’ holds all the temporary datasets generated by following macro calls.

Also, a configuration file named CLINCAP_configuration.sas put in C:Program FilesCDISC Expressprogramsstudy configuration. This file is used to set some study level parameters, such as lab and toxicity specifications (details in C:Program FilesCDISC ExpressspecsLab specs).

Two versions of SDTM implementation guides are supported by CDISC Express, CDISC SDTM Implementation Guide Version 3.1.1 and Version 3.1.2. You can find the corresponding specification files in C:Program FilesCDISC ExpressspecsSDTM specs:

SDTM_Specs_3_1_1.xls

SDTM_Specs_3_1_2.xls

The choosing of SDTM implementation version is also coded in the configuration file, in Line 41:

%LET SDTMSPECFILE=SDTM_Specs_3_1_1.xls;

Version 3.1.1 is used by default. You can also choose Version 3.1.2 if needed:

%LET SDTMSPECFILE=SDTM_Specs_3_1_2.xls;

Assign a study name and choose a SDTM implementation version. That’s all needed in step 1. Let’s take few minutes to navigate the software. CDISC Express is a set of macros and Excel files. It is important to know the file structure first:

C:Program FilesCDISC Express

├─documentation :FAQ, Quick Start, User Guide

├─macros

│ ├─ClinMap :system level macros

│ └─function_library :study level macros

├─programs :"action taken" macros

│ ├─study configuration :study parameters configuration

├─SDTM Validation :For validation of SDTM domains

│ └─study1

├─specs :specification files

│ ├─Excel engine :ExcelXP tagset file

│ ├─Lab specs :lab and toxicity

│ ├─Mapping validation :validation rules

│ ├─SDTM specs :hold two versions of SDTM implementation

│ └─SDTM Terminology :SDTM codelist(including NCI terminology)

├─studies

│ ├─example1

└─temp :hold temporary data not specified to any studies

As we already got, all the “action taken” programs such as create_new_study.sas are located in C:Program FilesCDISC Expressprograms. In create_new_study.sas, one macro is called, %addnewstudy, which is in C:Program FilesCDISC ExpressmacrosClinMap.

Note that in C:Program FilesCDISC Expressmacros, there are two sets of macros in different folders:

C:Program FilesCDISC ExpressmacrosClinMap: this folder holds all “system” level macros used by the application only. No modification encouraged.

C:Program FilesCDISC Expressmacrosfunction_library: macros used for mapping among studies. You can also create you own macro in this folder. The application imbedded macros also documented in user guide.

Following will be the most important part, mapping file.

TobeContinued

ADJECTIVE Encounters

Really she is the strangest creature in the world, far from heroic, variable as a weathercock, “bashful, insolent; chaste, lustful; prating, silent; laborious, delicate; ingenious, heavy; melancholic, pleasant; lying, true; knowing, ignorant; liberal, covetous, and prodigal”— in short, so complex, so indefinite, …

            –Virginia Woolf, The Common Reader, First Series (1925)

I don’t know if Ernest Hemingway is still one of the recognized dominant writers in colleges English education. At least once  a time he WAS. In an extreme form, he used only nouns and verbs to construct sentences.

In my personal English education(as Second Language), admittedly that there is also an absence of adjectives. It is just wonderful, nice, great, cool, weird, awesome,  and all in all, everything is OK or not OK, good or not good. In writing, my sentences lack of tone and shades. I write only technical articles in English and people can often well manage the so-called technical borings when acquiring information, knowledge, and opinions.

In reading when I try to just read for the sake of reading itself, I also find it is difficult to dig into pure literature pages where rich adjectives assembled heavily. I only read smoothly technical papers. So when I happened to have a paper book of Woolf and also loaded a corresponding public domain e-book in Kindle, I read the most intensive instances of adjectives ever. It is really totally different experiences.

There would be three types in any languages. For English:

in the top, pure literature, Shakespeare-like;

middle, which could be called the universal or international English; it is be the dominate English among nations in business, technology and even academia; most of the popular writers also utilize such sort of English to extent their global reputation;

bottom, the street language, slangs, talk-show.

I penetrate the English through the middle like almost all of the ESL learners. It is the most convenient and effective way in a very short run. But for a leap in long run, some friends just suggest that I should go down the street or climb up to the top. Ok I am on the way. Virginia Woolf is the first stop and I keep the first notes.