As per item #6 on yesterday's GPC-DEV call agenda...

? Alex will send out scripts in the next day or two.
    ? Volunteers to try by the next call: Hubert (Nebraska) via Jim,
    ? Wisconsin
    ? MCW / Glenn
...here is the script. I put all instructions and explanations I could think of into the comments, but I welcome questions or feedback via this mailing list or direct email as well.

Regarding metadata: if we define metadata as the contents of the I2B2METADATA or BLUEHERONMETADATA schema, then unfortunately that is at the moment outside the scope of this script. I do, however, agree with Phillip's point about the need to write something similar for the metadata schema, and welcome advice on what to put in it.

If we define metadata as modifiers and various data descriptor fields /within/ the data schema, you will find that those things get examined in considerable detail.

Thank you all very much.

/**
I originally started out writing a cohort-characterization script that could be
adapted to the needs of many different research project just by altering the 
cohort selection criteria (since most cohort characterization needs seem to 
follow the same basic pattern).

However, from UTHSCSA's own trial-and-error and from discussions on the mailing 
lists it has become clear that for multi-site counts to get non-misleading 
results we need to have a very detailed characterization of each site's I2B2 
semantics within its CRC (data repository) cell. The immediate goals of this 
script are:

* To create a sort of empirical data dictionary we can all refer to in 
  constructing federated queries that will run correctly at GPC sites as they 
  *currently* are, so that cohort characterization can proceed in parallel with 
  data standardization.
* To help those working on data standardization see what else needs to be done
  and where it needs to be done the most.
* To make certain data quality issues easier to spot.

This script ONLY does counts-- no individual records are extracted into the 
output tables. Also, no dates, patient_nums, or location information will end
up in the output tables.

This script was tested on SQL*Developer but will probably work in
a SQL*Plus console. The runtime at UTHSCSA was about 20 minutes.

INSTRUCTIONS:

1. Set the appropriate values for `dataschema` and `yoursite`. Please leave 
   `conceptinfo` as-is.
2. Run this script as a user who has select access to the schema you specified
   as your `dataschema`. That is the only external schema you'll need for this 
   version of the script. The user running this script will also need 
   permissions to create tables in their own schema. No tables will be created
   anywhere else. For some of the tables, SQL*Developer might output...
   "Failed: Warning: execution completed with warning"
   This might have something to do with ...
   https://support.oracle.com/rs?type=bug&id=7526579 
   Long story short, the tables do get created, so disregard the warning.
3. After running the script, please either export the tables OUTPUT_CON_MOD,
   OUTPUT_BASIC_DEMOG, and OUTPUT_PROVIDER to .csv files or view them in 
   SQL*Developer, select all, and copy-paste into spreadsheets.
4. Email to [email protected] or upload to Shared Queries/Site Semantics on
   PCORnet Central Desktop 
(https://pcornet.centraldesktop.com/c4gpc/folder/4212054/)

Thank you very, very much. Please let me know if you want a copy the GPC-wide
results or if you have questions or suggestions.
**/

/** the name of the schema where you keep your DE-identified data 
    (e.g. OBSERVATION_FACT) **/
define dataschema = BLUEHERONDATA;
/** A string by which to identify your site **/
define yoursite = UTHSCSA;

-- to empirically find out what data-type each concept_cd
define conceptinfo = "valtype_cd,tval_char,valueflag_cd,location_cd,units_cd";

/** temporary table needed for faster creation of the other tables **/
create table obs_temp as select distinct patient_num,start_date
,concept_cd,modifier_cd
,&conceptinfo
,case when nval_num is NULL then 'None' else 'Numeric' end nval
from &dataschema..observation_fact;

/** 
This is a table counting the number of occurrences for each combination of
CONCEPT_CD and its various attributes along with their paths and descriptions 
where available. Some uses for this table:

* What MODIFIER_CDs can accompany a given CONCEPT_CD? For example at UTHSCSA if
  I run ...
  SELECT * FROM OUTPUT_CON_MOD WHERE CONCEPT_CD = 'ICD9:135';
  ...I find that a substantial number of diagnoses are part of patient medical 
  history, so I definitely need to filter that out if I'm looking for 
  observations recorded on patients with active sarcoidosis.
* What categoric values exist for a given CONCEPT_CD?
  SELECT DISTINCT TVAL_CHAR FROM OUTPUT_CON_MOD WHERE CONCEPT_CD LIKE 
  '%COMPONENT_ID:2199' AND MODIFIER_CD = '@';
  Turns out this lab-value (a TSH screen) can have the following categoric 
  values: <0.1, <1.0, <2.90, <0.10, E, L, and TNP. 
* You can also figure out if and when this lab result has a numeric value:
  SELECT DISTINCT TVAL_CHAR,VALTYPE_CD,NVAL FROM OUTPUT_CON_MOD WHERE 
  CONCEPT_CD LIKE '%COMPONENT_ID:2199' AND MODIFIER_CD = '@';
  At UTHSCSA it looks like the VALTYPE_CD is correctly assigned for this lab, 
  and numeric values are available when TVAL_CHAR is either 'L' or 'E'.
* You can see what types of concepts are the most prevalent at a site... even if
  their metadata tables are out of date because this table doesn't use
  metadata, it's based directly on counts of OBSERVATION_FACT:
  SELECT CONCEPT_DOMAIN,SUM(N_VISITS) N_VISITS FROM
  (SELECT REGEXP_SUBSTR(CONCEPT_CD, '^.*:') CONCEPT_DOMAIN,N_VISITS 
  FROM OUTPUT_CON_MOD) GROUP BY CONCEPT_DOMAIN
  ORDER BY N_VISITS DESC;
  At UTHSCSA, it looks like DX_ID type diagnoses are more common than 
  ICD9 type diagnoses. Observations in the ALLERGEN domain are relatively rare
  and ones in the MicroPositive domain are very rare.
* You can globally see what types of concepts can occur with what types of 
  modifiers:
  SELECT DISTINCT REGEXP_SUBSTR(CONCEPT_CD, '^.*:') CONCEPT_DOMAIN,MODIFIER_CD 
  FROM OUTPUT_CON_MOD ORDER BY CONCEPT_DOMAIN, MODIFIER_CD;
  Looks like observations in the PAT_ENC domain never have modifiers, and most 
  modifiers for the MEDICATION_ID domain describe dosage.
* Let's say you're looking for _every_ type of observation having something to 
  do with Helicobactor pylori, so that you can look them over and decide what 
  filters to use in a federated query:
  SELECT * FROM OUTPUT_CON_MOD 
  WHERE LOWER(CONPATH) LIKE '%pylori%' OR LOWER(CONPATH) LIKE '%helico%' 
  OR LOWER(CONNAME) LIKE '%pylori%' OR LOWER(CONNAME) LIKE '%helico%';
  ...you get a number of different diagnoses and procedures and at the same time
  information about modifiers, data types, and counts.
* If you have CONNAMEs or CONPATHs that are null, it may mean that those 
  observatoins are orphaned and not visible from queries that depend on 
  metadata... but now you know about their existance and can query for them in
  OBSERVATION_FACT if they are relevant to your research.
**/

create table OUTPUT_CON_MOD as
with 
encs as (select concept_cd,modifier_cd, &conceptinfo,nval, count(*) N_visits
  from obs_temp 
  group by concept_cd,modifier_cd, &conceptinfo ,nval),
/** modifier_cd information, for readability/interpretability/finding orphaned 
modifiers**/
mods as (select modifier_cd modcd,name_char modname,modifier_path modpath 
  from &dataschema..modifier_dimension where modifier_cd is not NULL),
/** concept_cd information, for readability/interpretability/finding orphaned 
concepts  **/
cons as (select concept_cd concd,min(name_char) conname,min(concept_path) 
conpath
  from &dataschema..concept_dimension group by concept_cd)

select '&yoursite' site,encs.*,conpath,conname,modpath,modname
from encs
left join mods on encs.modifier_cd = mods.modcd
left join cons on encs.concept_cd = cons.concd
order by concept_cd,modifier_cd
;

/** This counts all existing distinct combinations of demographic variables
in patient_dimension and pivots over a range of years. The first 'year', 9998
actually represents ALL distinct patients in the database regardless of year. 
(e.g. total number of living female white english-speakers aged 6-12, total 
number of living male white english-speakers aged 6-12, and so on). The 
remaining columns are likewise, but counts of patients from each group seen only
during those respective years. There is a method to the madness for how the 
other years were chosen: 1988 is the earliest year for which any GPC reports 
having records. 2015 and 2016 haven't happened yet, so if you have any counts in
those years, you have a good reason to be curious. 9999 and 0 are the 'foo' and
'bar' of four digit integers, and might have been used by someone to tag 
missing or otherwise problematic records.
some uses for this table:
* Spotting nonsensical combinations of demographic values, like patients in the 
  1-5 age-range who have a marital status of 'divorced'
* Spotting negative ages or missing ages
* Spotting demographic variables that aren't used at a given site... for example
  at UTHSCSA, it looks like we don't currently use RELIGION_CD or INCOME_CD, so
  so if someone sends us a query that filters on those, they won't get any 
  results!
* Seeing what terms are valid for the various columns of PATIENT_DIMENSION at 
  each site... if your query filters on `SEX_CD = 'f'` and a given site uses 
  values of 'Male','Female', and '@' for SEX_CD, that's another query that 
won't 
  return results.
* Collapsing it into a more manageable pivot table and using it to see which 
  years have the most patients in the demographic groups that interest you. For
  example, here's how you would do an age breakdown for the years 2012-2014:
  SELECT 
AGE_RANGE,SUM("2012_PATIENTS"),SUM("2013_PATIENTS"),SUM("2014_PATIENTS")
  FROM OUTPUT_BASIC_DEMOG GROUP BY AGE_RANGE ORDER BY AGE_RANGE;
  ...or race and age:
  SELECT 
RACE,AGE_RANGE,SUM("2012_PATIENTS"),SUM("2013_PATIENTS"),SUM("2014_PATIENTS")
  FROM OUTPUT_BASIC_DEMOG GROUP BY RACE,AGE_RANGE ORDER BY RACE,AGE_RANGE;  
**/

create table OUTPUT_BASIC_DEMOG as
with
eachyr as (select distinct patient_num pn,extract(year from start_date) yr 
           from obs_temp),
allyrs as (select * from eachyr union all 
           select distinct pn,9998 yr from eachyr)           
select * from (select '&yoursite' SITE,yr,VITAL_STATUS_CD,SEX_CD
  ,case when AGE_IN_YEARS_NUM is NULL then 'Missing'
   when AGE_IN_YEARS_NUM < 0 then 'Negative'
   when AGE_IN_YEARS_NUM < 1 then '< 1'
   when AGE_IN_YEARS_NUM <= 5 then '1-5'
   when AGE_IN_YEARS_NUM <= 12 then '6-12'
   when AGE_IN_YEARS_NUM <= 18 then '13-18'
   when AGE_IN_YEARS_NUM <= 65 then '19-65'
   when AGE_IN_YEARS_NUM <= 85 then '66-85'
   else '>85' end age_range
  ,LANGUAGE_CD,RACE_CD,MARITAL_STATUS_CD,RELIGION_CD,INCOME_CD 
  from allyrs left join &dataschema..patient_dimension pat
  on allyrs.pn = pat.patient_num)
pivot( count(*) patients FOR yr 
IN ( 9998,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998
    ,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010
    ,2011,2012,2013,2014,2015,2016,9999,0))
order by age_range,sex_cd,race_cd
;

/** Surveying to see for what if anything people are using the columns within 
VISIT_DIMENSION. Might identify sites where it's possible to query by clinic or
by visit type.
**/
create table OUTPUT_VISIT as
select '&yoursite' site,ACTIVE_STATUS_CD,INOUT_CD
,LOCATION_CD,LOCATION_PATH
,min(LENGTH_OF_STAY)+0 minstay,max(LENGTH_OF_STAY)+0 maxstay,count(*) N
from &dataschema..visit_dimension 
group by ACTIVE_STATUS_CD,INOUT_CD,LOCATION_CD,LOCATION_PATH;

/** If your sites has provider information, this counts distinct patients by 
provider. At least, that's what I hope it does: UTHSCSA's PROVIDER_DIMENSION is
currently empty. :-(
**/
create table OUTPUT_PROVIDER as
select '&yoursite' site,count(*) patients
,min(provider_path) provider_path,min(name_char) name_char
from (select distinct provider_id pi,patient_num 
  from &dataschema..observation_fact) obs
left join &dataschema..provider_dimension prv
on obs.pi = prv.provider_id
where obs.pi is not NULL
group by pi;

/** cleanup **/
drop table OBS_TEMP;
/*
drop table OUTPUT_CON_MOD;
drop table OUTPUT_BASIC_DEMOG;
drop table OUTPUT_PROVIDER;
drop table OUTPUT_VISIT;
*/
_______________________________________________
Gpc-dev mailing list
[email protected]
http://listserv.kumc.edu/mailman/listinfo/gpc-dev

Reply via email to