Analiza datos de FHIR en BigQuery

En este documento, se explica a los investigadores, los científicos de datos y los analistas de datos los procesos y las consideraciones para analizar los datos de los recursos para una interoperabilidad de atención médica rápida (FHIR) en BigQuery..

En particular, este documento se centra en los datos de los recursos de pacientes que se exportan desde el almacén de FHIR en la API de Cloud Healthcare. En este documento, también se explica una serie de consultas que demuestran cómo funcionan los datos del esquema de FHIR en un formato relacional y se muestra cómo acceder a estas consultas para volver a usarlas mediante vistas.

Usa BigQuery para analizar datos de FHIR

La API específica de FHIR de la API de Cloud Healthcare está diseñada para la interacción de transacción en tiempo real con datos FHIR a nivel de un solo recurso de FHIR o un conjunto de recursos de FHIR. Sin embargo, la API de FHIR no está diseñada para casos de uso de estadísticas. Para estos casos de uso, recomendamos exportar tus datos desde la API de FHIR a BigQuery. BigQuery es un almacén de datos escalable y sin servidores que te permite analizar grandes cantidades de datos de manera retrospectiva o prospectiva.

Además, BigQuery cumple con ANSI:2011 SQL, que permite que los científicos de datos y analistas empresariales accedan a los datos mediante herramientas que suelen usar, como Tableau, Looker o Vertex AI Workbench.

En algunas aplicaciones, como Vertex AI Workbenchs, obtienes acceso a través de clientes integrados, como la biblioteca cliente de Python para BigQuery. En estos casos, los datos que se muestran a la aplicación están disponibles a través de estructuras de datos de lenguaje integradas.

Accede a BigQuery

Puedes acceder a BigQuery a través de la IU web de BigQuery en la consola de Google Cloud y, también, con las siguientes herramientas:

Con estas herramientas, puedes integrar BigQuery en casi cualquier aplicación.

Trabaja con la estructura de datos de FHIR

La estructura de datos estándar de FHIR integrada es compleja, y tiene tipos de datos FHIR incorporados y anidados en cualquier recurso de FHIR. Estos tipos de datos FHIR incorporables se denominan tipos de datos complejos. Los arreglos y las estructuras también se denominan tipos de datos complejos en las bases de datos relacionales. La estructura de datos de FHIR estándar incorporada funciona bien serializada como archivos XML o JSON en un sistema orientado a los documentos, pero puede ser difícil operar con la estructura cuando se traduce en bases de datos relacionales.

En la siguiente captura de pantalla, se muestra una vista parcial de un tipo de datos patient resource de FHIR que ilustra la naturaleza compleja de la estructura de datos estándar de FHIR incorporada.

Tipo de datos de “recursos de pacientes” de FHIR

En la captura de pantalla anterior, se muestran los componentes principales de un tipo de datos patient resource de FHIR. Por ejemplo, la columna cardinalidad (indicada en la tabla como Card.) muestra varios elementos que pueden tener cero entradas, una o más de una. La columna Tipo muestra los tipos de datos Identifier, HumanName y Address, que son ejemplos de tipos de datos complejos que incluyen el tipo patient resource. Cada una de estas filas puede registrarse varias veces, como un arreglo de estructuras.

Usa arreglos y estructuras

BigQuery admite arreglos y tipos de datos STRUCT (estructuras de datos repetidas y anidadas), ya que se representan en recursos FHIR, lo que permite la conversión de datos de FHIR a BigQuery para ver sus detalles.

En BigQuery, un arreglo es una lista ordenada que consta de cero o más valores del mismo tipo de datos. Puedes construir arreglos de tipos de datos simples, como el tipo de datos INT64, y de tipos de datos complejos, como el tipo de datos STRUCT. La excepción es el tipo de datos ARRAY, porque en este momento no se admiten arreglos de arreglos. En BigQuery, un arreglo de estructuras aparece como un registro que se puede repetir.

Puedes especificar datos anidados, o datos anidados y repetidos, en la IU de BigQuery o en un archivo de esquema JSON. Para especificar columnas anidadas, o columnas anidadas y repetidas, usa el tipo de datos RECORD (STRUCT).

La API de Cloud Healthcare es compatible con el esquema de SQL en FHIR en BigQuery. Este esquema de estadísticas es el esquema predeterminado en el método ExportResources() y lo respalda la comunidad de FHIR.

BigQuery admite datos desnormalizados. Esto significa que cuando almacenas tus datos, en lugar de crear un esquema relacional como un esquema estrella o copo de nieve, puedes desnormalizar tus datos y usar columnas anidadas y repetidas. Las columnas anidadas y repetidas mantienen las relaciones entre los elementos de datos sin el impacto en el rendimiento que genera preservar un esquema relacional (normalizado).

Accede a tus datos a través del operador UNNEST

Cada recurso de FHIR en la API de FHIR se exporta a BigQuery como una fila de datos. Puedes considerar un arreglo o la estructura de las filas como una tabla incorporada. Puedes acceder a los datos de esa “tabla” en la cláusula SELECT o en la WHERE de tu consulta si compactas el arreglo o la estructura con el operador UNNEST. El operador UNNEST toma un arreglo y, luego, muestra una tabla con una sola fila para cada elemento del arreglo. Para obtener más información, consulta cómo trabajar con arreglos en SQL estándar.

La operación UNNEST no conserva el orden de los elementos del arreglo, pero puedes reordenar la tabla con la cláusula opcional WITH OFFSET. Con esto, se muestra una columna adicional con la cláusula OFFSET por cada elemento del arreglo. Luego, puedes usar la cláusula ORDER BY para ordenar las filas por su desplazamiento.

Cuando unes datos no anidados, BigQuery usa una operación correlacionada CROSS JOIN que alude a la columna de arreglos de cada elemento del arreglo con la tabla de origen, que es la tabla que precede directamente a la llamada a UNNEST en la cláusula FROM. En cada fila de la tabla de origen, la operación UNNEST compacta el arreglo en un conjunto de filas que contienen los elementos del arreglo. La operación correlacionada CROSS JOIN se une a este conjunto nuevo de filas con la fila única de la tabla de origen.

Investiga el esquema mediante consultas

Para consultar datos de FHIR en BigQuery, es importante comprender el esquema que se crea mediante del proceso de exportación. BigQuery te permite inspeccionar la estructura de las columnas de cada tabla del conjunto de datos con la función INFORMATION_SCHEMA, una serie de vistas en las que se muestran metadatos. En el resto de este documento, se hace referencia al esquema SQL en FHIR, que está diseñado para ser accesible a fin de recuperar datos.

La siguiente consulta de muestra explora los detalles de la columna de la tabla de pacientes en el esquema de SQL en FHIR. La consulta hace referencia al conjunto de datos públicos de los datos sintéticos que genera Synthea en FHIR, que aloja más de 1 millón de registros de pacientes sintéticos generados en formatos de Synthea y FHIR.

Cuando consultas la vista INFORMATION_SCHEMA.COLUMNS, los resultados de la consulta contienen una fila por cada columna (campo) de una tabla. La siguiente consulta muestra todas las columnas de la tabla de pacientes:

SELECT *
FROM `bigquery-public-data.fhir_synthea.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name='patient'

En la siguiente captura de pantalla del resultado de la consulta, se muestran el tipo de datos identifier y su arreglo que contiene los tipos de datos STRUCT.

El tipo de datos de “identificador” y el arreglo dentro del tipo de datos que contiene el tipo de datos “STRUCT”

Usa el recurso de pacientes de FHIR en BigQuery

El número de historia clínica (MRN) de los pacientes, un dato crítico almacenado en tus datos de FHIR, se usa en los sistemas de datos clínicos y operativos de una organización de todos los pacientes. Cualquier método de acceso a los datos de un paciente individual o un conjunto de pacientes debe filtrar o mostrar el MRN, o hacer ambas acciones.

Mediante la siguiente consulta de muestra, se muestra el identificador del servidor de FHIR interno al recurso de pacientes, incluido el MRN y la fecha de nacimiento de todos los pacientes. El filtro para consultar en un MRN específico también se incluye, pero se analiza en este ejemplo.

En esta consulta, debes desanidar el tipo de datos complejo identifier dos veces. También debes usar las operaciones CROSS JOIN correlacionadas para unir datos sin anidar con su tabla de origen. La tabla bigquery-public-data.fhir_synthea.patient en la consulta se creó mediante el SQL en la versión del esquema de FHIR de la exportación de FHIR a BigQuery.

SELECT id, i.value as MRN, birthDate
FROM `bigquery-public-data.fhir_synthea.patient`
#This is a correlated cross join
,UNNEST(identifier) i
,UNNEST(i.type.coding) it
WHERE
# identifier.type.coding.code
it.code = "MR"
#uncomment to get data for one patient, this MRN exists
#AND i.value = "a55c8c2f-474b-4dbd-9c84-effe5c0aed5b"

El resultado es similar al siguiente:

Resultado que muestra el ID, el número de la historia clínica y la fecha de nacimiento para el tipo de identificador configurado como MR

En la consulta anterior, el conjunto de valores identifier.type.coding.code es el conjunto de valores identifier de FHIR que enumera los tipos de datos de identidad disponibles, como el MRN (el tipo de datos de identidad MR), la licencia de conducir (el tipo de datos de identidad DL) y el número de pasaporte (el tipo de datos de identidad PPN). Debido a que el conjunto de valores identifier.type.coding es un arreglo, puede haber cualquier cantidad de identificadores enumerados para un paciente. Sin embargo, en este caso, deseas obtener el MRN (el tipo de datos de identidad MR).

Une la tabla de pacientes con otras tablas

A partir de la consulta de la tabla de pacientes, puedes unir la tabla de pacientes con otras tablas de este conjunto de datos, como la tabla de afecciones. En la tabla de afecciones, se registran los diagnósticos de los pacientes.

La siguiente consulta de muestra recupera todas las entradas para la afección médica de hipertensión.

SELECT abatement.dateTime as abatement_dateTime, assertedDate, category, clinicalStatus, code, onset.dateTime as onset_dateTime, subject.patientid
FROM
`bigquery-public-data.fhir_synthea.condition`
,UNNEST(code.coding) as code
WHERE
code.system = 'http://snomed.info/sct'
#snomed code for Hypertension
AND code.code = '38341003'

El resultado es similar al siguiente:

Resultado que muestra registros de afección médica de hipertensión

En la consulta anterior, vuelve a usar el método UNNEST para compactar el campo code.coding. Los elementos de código abatement.dateTime y onset.dateTime de la sentencia SELECT tienen alias, ya que ambos terminan en dateTime, lo que daría como resultado nombres de columnas ambiguos en el resultado de una sentencia SELECT. Cuando seleccionas el código Hypertension, también debes declarar el sistema de terminología del que proviene el código, que en este caso es el sistema de terminología clínica SNOMED CT.

Como paso final, usa la clave subject.patientid para unir la tabla de afecciones con la tabla de pacientes. Esta clave apunta al identificador del recurso de los pacientes en el servidor de FHIR.

Cómo unir las consultas

En la siguiente consulta de muestra, debes usar las consultas de las dos secciones anteriores y unirlas mediante la cláusula WITH, mientras realizas algunos cálculos simples.

WITH patient AS (
SELECT id as patientid, i.value as MRN, birthDate
FROM
`bigquery-public-data.fhir_synthea.patient`
#This is a correlated cross join
,UNNEST(identifier) i
,UNNEST(i.type.coding) it
WHERE
# identifier.type.coding.code
it.code = "MR"
#uncomment to get data for one patient, this MRN exists
#AND i.value = "a55c8c2f-474b-4dbd-9c84-effe5c0aed5b"
),
condition AS (
SELECT abatement.dateTime as abatement_dateTime, assertedDate, category, clinicalStatus, code, onset.dateTime as onset_dateTime, subject.patientid
FROM
`bigquery-public-data.fhir_synthea.condition`
,UNNEST(code.coding) as code
WHERE
code.system = 'http://snomed.info/sct'
#snomed code for Hypertension
AND code.code = '38341003'
)
SELECT patient.patientid, patient.MRN, patient.birthDate as birthDate_string,
#current patient age. now - birthdate
CAST(DATE_DIFF(CURRENT_DATE(),CAST(patient.birthDate AS DATE),MONTH)/12 AS INT) as patient_current_age_years,
CAST(DATE_DIFF(CURRENT_DATE(),CAST(patient.birthDate AS DATE),MONTH) AS INT) as patient_current_age_months,
CAST(DATE_DIFF(CURRENT_DATE(),CAST(patient.birthDate AS DATE),DAY) AS INT) as patient_current_age_days,
#age at onset. onset date - birthdate
DATE_DIFF(CAST(SUBSTR(condition.onset_dateTime,1,10) AS DATE),CAST(patient.birthDate AS DATE),YEAR)as patient_age_at_onset,
condition.onset_dateTime, condition.code.code, condition.code.display, condition.code.system
FROM patient JOIN condition
ON patient.patientid = condition.patientid

El resultado es similar al siguiente:

Resultado que muestra el ID, el MRN y la fecha de nacimiento para la afección médica de hipertensión.

En la consulta de la muestra anterior, la cláusula WITH te permite aislar subconsultas en sus propios segmentos definidos. Este enfoque puede ayudar con la legibilidad, que se vuelve más importante a medida que tu consulta crece. En esta consulta, se aisla la subconsulta sobre pacientes y afecciones en sus propios segmentos de WITH y, luego, se une en el segmento principal SELECT.

También puedes aplicar cálculos a datos sin procesar. El siguiente código de muestra, una declaración SELECT, se muestra cómo calcular la edad del paciente cuando comenzó con una enfermedad.

DATE_DIFF(CAST(SUBSTR(condition.onset_dateTime,1,10) AS DATE),CAST(patient.birthDate AS DATE),YEAR)as patient_age_at_onset

Como se indica en el ejemplo de código anterior, puedes realizar una serie de operaciones en la string dateTime proporcionada, condition.onset_dateTime. Primero, selecciona el componente de fecha de la string con el valor SUBSTR. Luego, debes convertir la string en un tipo de datos DATE con la sintaxis CAST. También debes convertir el campo patient.birthDate en el campo DATE. Por último, calcula la diferencia entre las dos fechas con la función DATE_DIFF.

¿Qué sigue?