library(reportROC)
library(kableExtra)
tar_load(site_perf_75)
tar_load(oper_indic_perf)
tar_load(eutirads_perf)
gbarplot <- function(.df, dep_var=malignant, by_var, title="") {
dep_var <- enquo(dep_var)
by_var <- enquo(by_var)
.df %>%
group_by(!! by_var) %>%
summarise(avg = round(mean(!! dep_var),2 )) %>%
ggplot(aes(x=!! by_var, y=avg)) +
geom_bar(stat="identity") +
geom_text(aes(label=avg), vjust = 1.5, color = "white") +
labs(x=as_label(by_var), y=paste0("mean of `",as_label(dep_var), "`"))
}
malignantopis zmiennej malignant, zob. tutaj. NIFTP traktowana jako malignant
df %>%
select(
malignant
) %>%
tbl_summary(
label = list(malignant ~ "Malignant diagnosis")
) %>%
modify_caption("**Table. Histological main diagnosis category**")
| Characteristic | N = 20,7621 |
|---|---|
| Malignant diagnosis | 7,907 (38%) |
| 1 n (%) | |
df %>%
gbarplot(by_var = sex)

df %>%
gbarplot(by_var=age_cat)

df %>%
gbarplot(by_var=beth_class)

df %>%
gbarplot(by_var=eutirads)

EU-TIRADS
malignant TR1 TR2 TR3 TR4 TR5
False 1136 2169 3337 4572 1641
True 93 243 796 2259 4516
reportROC(gold=df$malignant, predictor = as.integer(df$eutirads))

Cutoff AUC AUC.SE AUC.low AUC.up P ACC ACC.low ACC.up SEN
4.500 0.778 0.003 0.772 0.784 0.000 0.758 0.758 0.758 0.571
SEN.low SEN.up SPE SPE.low SPE.up PLR PLR.low PLR.up NLR
0.560 0.582 0.872 0.867 0.878 4.474 4.260 4.699 0.492
NLR.low NLR.up PPV PPV.low PPV.up NPV NPV.low NPV.up PPA
0.479 0.505 0.733 0.722 0.745 0.768 0.761 0.775 0.571
PPA.low PPA.up NPA NPA.low NPA.up TPA TPA.low TPA.up KAPPA
0.560 0.582 0.872 0.867 0.878 0.758 0.752 0.763 0.463
KAPPA.low KAPPA.up
0.451 0.476
eutirads_perf %>%
select(
everything(),
-"P",
-"AUC.SE",
-starts_with(c("PPA", "NPA","TPA", "KAPPA"))
) %>%
select(
-ends_with(c(".up",".low"))
) %>% kable() %>% kable_styling()
| threshold | crit_benign | crit_malign | AUC | ACC | SEN | SPE | PLR | NLR | PPV | NPV |
|---|---|---|---|---|---|---|---|---|---|---|
| 2 | TR1-1 | TR2-5 | 0.538 | 0.431 | 0.988 | 0.088 | 1.084 | 0.133 | 0.400 | 0.924 |
| 3 | TR1-2 | TR3-5 | 0.607 | 0.524 | 0.958 | 0.257 | 1.289 | 0.165 | 0.442 | 0.908 |
| 4 | TR1-3 | TR4-5 | 0.687 | 0.646 | 0.857 | 0.517 | 1.773 | 0.277 | 0.522 | 0.854 |
| 5 | TR1-4 | TR5-5 | 0.722 | 0.758 | 0.571 | 0.872 | 4.474 | 0.492 | 0.733 | 0.768 |
Oznaczenia: crit_benign / crit_malign – kryterium łagodności / złośliwości, AUC = Area Under Curve, ACC = accuracy, SEN = sensitivity, SPE = specificity, PLR = positive likelihood ratio, NLR = negative likelihood ratio, PPV = positive predictive value, NPV = negative predictive value.
EU-TIRADS
malignant TR1-4 TR5
False 11214 1641
True 3391 4516
reportROC(gold=df$malignant, predictor.binary = df$eutirads_5, plot = FALSE)
AUC AUC.SE AUC.low AUC.up P ACC ACC.low ACC.up SEN SEN.low
0.722 0.004 0.713 0.730 0.000 0.758 0.758 0.758 0.571 0.560
SEN.up SPE SPE.low SPE.up PLR PLR.low PLR.up NLR NLR.low
0.582 0.872 0.867 0.878 4.474 4.260 4.699 0.492 0.479
NLR.up PPV PPV.low PPV.up NPV NPV.low NPV.up PPA PPA.low
0.505 0.733 0.722 0.745 0.768 0.761 0.775 0.571 0.560
PPA.up NPA NPA.low NPA.up TPA TPA.low TPA.up KAPPA KAPPA.low
0.582 0.872 0.867 0.878 0.758 0.752 0.763 0.463 0.451
KAPPA.up
0.476
Diagnostic Odds Ratio and 95% CI
2.5 % 97.5 %
9.1 8.5 9.7
Nie martwimy się niską sensitivity dla Eutirads 5 i dużą ilością wyników FalseNeg Bo właśnie to sprzyja ograniczaniu overtreatment
I dobrze że tak właśnie wyszło, takiego narzędzia potrzebujemy (zakładając że działałoby tak samo w populacji osób z chorobami tarczycy - a nie tylko tych operowanych )
Może warto przyjrzeć się kim byli ci pacjenci FalseNeg? Może to w 99 procentach osoby z nowotworami low-risk? (edited)
tpos_vs_tneg |>
pivot_wider(id_cols = -c(group_percent), names_from = clinical_risk, values_from =n ) |>
kable(
caption = "N = 7907 malignant tumors"
) |>
kable_styling()
| eutirads_5 | group_n | high | intermediate | low |
|---|---|---|---|---|
| 0 | 3391 | 155 | 1936 | 1300 |
| 1 | 4516 | 848 | 2361 | 1307 |
tpos_vs_tneg |>
pivot_wider(id_cols = -c(n), names_from = clinical_risk, values_from = group_percent ) |>
kable(
caption = "N = 7907 malignant tumors",
digits = 1
) |>
kable_styling()
| eutirads_5 | group_n | high | intermediate | low |
|---|---|---|---|---|
| 0 | 3391 | 4.6 | 57.1 | 38.3 |
| 1 | 4516 | 18.8 | 52.3 | 28.9 |
czyli gdyby w naszej populacji badanej* wykorzystać EU-TIRADS 5 jako predyktor obecności nowotworu złośliwego, to nie wykryjemy 3391 / 7909 = 42.9% raków, z których 4.6% to raki wysokiego ryzyka
(populacja u nas = osoby dorosłe zakwalifikowane do operacji tarczycy)
excluded sites with < 75 operations
ggplot(site_perf_75, aes(x=AUC)) + geom_histogram(bins=9)

# A tibble: 1 × 5
n_sites n_oper auc_p50 auc_min auc_max
<int> <int> <dbl> <dbl> <dbl>
1 37 18944 0.752 0.626 0.895
ggplot(site_perf_75, aes(x=n, y=AUC)) + geom_point() + scale_x_continuous(trans = "log") + geom_smooth(method = "lm")

na tym wykresie każda kropka to 1 ośrodek na osi X liczba zgłoszonych operacji na osi Y wartość AUC ROC dla skali TIRADS
AUC ROC mierzy jak dobrze TIRADS w danym ośrodku sprawdza się w odróżnianiu nowotw. łagodnych i złośliwych
z takiego obrazka wynika, że
ośrodki różnią się znacznie pod względem AUC ROC (są takie ośrodki dla których AUC ROC < 0.7 ale też są też takie, dla których prawie 0.9) oraz pod względem wolumenu.
no i że jest jakaś korelacja pomiędzy wolumenem operacji a skutecznością TIRADS w identyfikacji nowotw złośliwych
oper_indic_perf %>%
select(oper_indic, n, starts_with("AUC"))
# A tibble: 5 × 6
oper_indic n AUC AUC.SE AUC.low AUC.up
<fct> <int> <dbl> <dbl> <dbl> <dbl>
1 Excluding malignancy 8842 0.558 0.006 0.547 0.569
2 Malignancy 4235 0.674 0.019 0.636 0.712
3 Compression symptom 4318 0.553 0.014 0.527 0.58
4 Thyreotoxicosis 2817 0.66 0.019 0.622 0.698
5 Other 550 0.621 0.03 0.562 0.68

EU-TIRADS guidelines recommend FNAB for:
category 5, nodules ˃10, or ˂10 mm when suspicious lymph nodes are present;
category 4, nodules ˃15 mm;
category 3, nodules ˃20 mm.
Category 2 nodules are not indicated for FNAB
W dostępnych danych informacja o wymiarze guzka jest dostępna jedynie dla nowotworów złośliwych
Nie mamy informacji czy w przedoperacyjnej ocenie USG stwierdzano podejrzane węzły chłonne.
df %>%
group_by(malignant) %>%
skim_without_charts(size_main)
| Name | Piped data |
| Number of rows | 20762 |
| Number of columns | 44 |
| _______________________ | |
| Column type frequency: | |
| numeric | 1 |
| ________________________ | |
| Group variables | malignant |
Variable type: numeric
| skim_variable | malignant | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 |
|---|---|---|---|---|---|---|---|---|---|---|
| size_main | 0 | 12839 | 0.00 | 16.44 | 19.96 | 0 | 5.75 | 8.5 | 20.25 | 80 |
| size_main | 1 | 245 | 0.97 | 15.87 | 14.13 | 0 | 7.00 | 12.0 | 20.00 | 150 |
df %>%
#filter(!is.na(fna_indicated)) %>%
count(malignant, fna_indicated) %>%
pivot_wider(names_from=malignant, values_from=n, values_fill = NA) %>%
kable() %>%
kable_styling()
| fna_indicated | 0 | 1 |
|---|---|---|
| 0 | 10 | 4145 |
| 1 | 6 | 3517 |
| NA | 12839 | 245 |
#%>%
# add_header_above(c(" ", "malignant"=2))
Czyli:
wśród n=7907 pacjentów z nowotw. złośl. ocena wskazań do BAC wg algorytmu EU-TIRADS była możliwa u 7662 (dla 245 fna_indicated == NA, co wynika z braku wprowadzonej wielkości guza, zm. size_main)
U 4145 z tych 7662 osób (54.1) nie było wskazań do BAC.
U ponad 50% pacjentów z histopatologicznie potwierdzonym nowotworem złośliwym tarczycy nie było wskazań do BAC wg algorytmu EU-TIRADS ??.
df %>%
filter(
malignant == 1,
# !is.na(fna_indicated) # usuń missing data
) %>%
select(
fna_indicated,
age,
sex,
oper_indic,
oper_type_th_aggr,
oper_type_ln_aggr
) %>%
mutate(
fna_indicated = factor(fna_indicated),
fna_indicated = fct_recode(fna_indicated, `recommended` = "1", `not recommended`="0"),
fna_indicated = fct_explicit_na(fna_indicated),
) %>%
tbl_summary(by=fna_indicated) %>%
add_overall() %>%
modify_caption(paste0("**Table. Clinical by EU-TIRADS FNA indications status<br>(n=",sum(df$malignant)," malignant thyroid tumors)**"))
| Characteristic | Overall, N = 7,9071 | not recommended, N = 4,1451 | recommended, N = 3,5171 | (Missing), N = 2451 |
|---|---|---|---|---|
| age | 48 (37, 60) | 49 (38, 60) | 47 (36, 61) | 51 (40, 65) |
| sex | ||||
| Male | 1,614 (20%) | 702 (17%) | 849 (24%) | 63 (26%) |
| Female | 6,293 (80%) | 3,443 (83%) | 2,668 (76%) | 182 (74%) |
| oper_indic | ||||
| Excluding malignancy | 3,030 (38%) | 1,661 (40%) | 1,238 (35%) | 131 (53%) |
| Malignancy | 4,053 (51%) | 1,885 (45%) | 2,119 (60%) | 49 (20%) |
| Compression symptom | 462 (5.8%) | 298 (7.2%) | 118 (3.4%) | 46 (19%) |
| Thyreotoxicosis | 231 (2.9%) | 195 (4.7%) | 26 (0.7%) | 10 (4.1%) |
| Other | 131 (1.7%) | 106 (2.6%) | 16 (0.5%) | 9 (3.7%) |
| oper_type_th_aggr | ||||
| Lobectomy | 3,022 (38%) | 1,740 (42%) | 1,182 (34%) | 100 (41%) |
| Other | 449 (5.7%) | 264 (6.4%) | 162 (4.6%) | 23 (9.4%) |
| Thyroidectomy | 4,436 (56%) | 2,141 (52%) | 2,173 (62%) | 122 (50%) |
| oper_type_ln_aggr | ||||
| None | 4,022 (51%) | 2,389 (58%) | 1,469 (42%) | 164 (67%) |
| CLND | 2,641 (33%) | 1,325 (32%) | 1,260 (36%) | 56 (23%) |
| CLND + LLND | 946 (12%) | 272 (6.6%) | 663 (19%) | 11 (4.5%) |
| Other | 298 (3.8%) | 159 (3.8%) | 125 (3.6%) | 14 (5.7%) |
| 1 Median (IQR); n (%) | ||||
df %>%
filter(
malignant == 1,
# !is.na(fna_indicated) # usuń missing data
) %>%
select(
fna_indicated,
tnm_t,
tnm_n,
tnm_m
) %>%
mutate(
fna_indicated = factor(fna_indicated),
fna_indicated = fct_recode(fna_indicated, `recommended` = "1", `not recommended`="0"),
fna_indicated = fct_explicit_na(fna_indicated),
# tnm_t = fct_drop(tnm_t), # nie wyświetlaj missing data
tnm_t = fct_recode(tnm_t,
"pT1a"="pT1a",
"pT1b"="pT1b",
"pT2"="pT2",
"pT3a"="pT3a",
"pT3b-pT4b"="pT3b",
"pT3b-pT4b"="pT4a",
"pT3b-pT4b"="pT4b",
"pTx"="pTx",
"pTx"="(Missing)",
"pTx"="pT0"
),
tnm_t = fct_relevel(tnm_t, "pTx", after=Inf)
) %>%
tbl_summary(by=fna_indicated) %>%
add_overall() %>%
modify_caption(paste0("**Table. TNM stage by EU-TIRADS FNA indications status<br>(n=",sum(df$malignant)," malignant thyroid tumors)**"))
| Characteristic | Overall, N = 7,9071 | not recommended, N = 4,1451 | recommended, N = 3,5171 | (Missing), N = 2451 |
|---|---|---|---|---|
| tnm_t | ||||
| pT1a | 3,502 (44%) | 3,407 (82%) | 42 (1.2%) | 53 (22%) |
| pT1b | 2,260 (29%) | 607 (15%) | 1,629 (46%) | 24 (9.8%) |
| pT2 | 1,267 (16%) | 57 (1.4%) | 1,198 (34%) | 12 (4.9%) |
| pT3a | 425 (5.4%) | 34 (0.8%) | 383 (11%) | 8 (3.3%) |
| pT3b-pT4b | 301 (3.8%) | 38 (0.9%) | 258 (7.3%) | 5 (2.0%) |
| pTx | 152 (1.9%) | 2 (<0.1%) | 7 (0.2%) | 143 (58%) |
| tnm_n | ||||
| pN0 | 3,497 (44%) | 2,076 (50%) | 1,385 (39%) | 36 (15%) |
| pN1a | 1,414 (18%) | 636 (15%) | 768 (22%) | 10 (4.1%) |
| pN1b | 787 (10.0%) | 197 (4.8%) | 574 (16%) | 16 (6.5%) |
| pNx | 2,209 (28%) | 1,236 (30%) | 790 (22%) | 183 (75%) |
| (Missing) | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) |
| tnm_m | ||||
| pM0 | 6,535 (83%) | 3,521 (85%) | 2,967 (84%) | 47 (19%) |
| pM1 | 96 (1.2%) | 12 (0.3%) | 80 (2.3%) | 4 (1.6%) |
| pMx | 1,276 (16%) | 612 (15%) | 470 (13%) | 194 (79%) |
| (Missing) | 0 (0%) | 0 (0%) | 0 (0%) | 0 (0%) |
| 1 n (%) | ||||
df %>%
filter(
malignant == 1,
# !is.na(fna_indicated) # usuń missing data
) %>%
select(
fna_indicated,
beth_class
) %>%
mutate(
fna_indicated = factor(fna_indicated),
fna_indicated = fct_recode(fna_indicated, `recommended` = "1", `not recommended`="0"),
fna_indicated = fct_explicit_na(fna_indicated),
) %>%
tbl_summary(by=fna_indicated) %>%
add_overall() %>%
modify_caption(paste0("**Table. Bethesda class by EU-TIRADS FNA indications status<br>(n=",sum(df$malignant)," malignant thyroid tumors)**"))
| Characteristic | Overall, N = 7,9071 | not recommended, N = 4,1451 | recommended, N = 3,5171 | (Missing), N = 2451 |
|---|---|---|---|---|
| beth_class | ||||
| Not performed | 734 (9.3%) | 472 (11%) | 227 (6.5%) | 35 (14%) |
| I | 89 (1.1%) | 54 (1.3%) | 31 (0.9%) | 4 (1.6%) |
| II | 374 (4.7%) | 256 (6.2%) | 77 (2.2%) | 41 (17%) |
| III | 422 (5.3%) | 235 (5.7%) | 153 (4.4%) | 34 (14%) |
| IV | 1,834 (23%) | 1,014 (24%) | 754 (21%) | 66 (27%) |
| V | 1,043 (13%) | 503 (12%) | 502 (14%) | 38 (16%) |
| VI | 3,411 (43%) | 1,611 (39%) | 1,773 (50%) | 27 (11%) |
| 1 n (%) | ||||
df %>%
filter(
malignant == 1,
# !is.na(fna_indicated) # usuń missing data
) %>%
select(
fna_indicated,
diagn_main
) %>%
mutate(
fna_indicated = factor(fna_indicated),
fna_indicated = fct_recode(fna_indicated, `recommended` = "1", `not recommended`="0"),
fna_indicated = fct_explicit_na(fna_indicated),
diagn_main = fct_infreq(diagn_main),
diagn_main = fct_lump_prop(diagn_main, prop = 0.01)
) %>%
tbl_summary(by=fna_indicated) %>%
add_overall() %>%
modify_caption(paste0("**Table. Histopathological type by EU-TIRADS FNA indications status<br>(n=",sum(df$malignant)," malignant thyroid tumors)**"))
| Characteristic | Overall, N = 7,9071 | not recommended, N = 4,1451 | recommended, N = 3,5171 | (Missing), N = 2451 |
|---|---|---|---|---|
| diagn_main | ||||
| Papillary cancer T-96 M-82603 | 6,766 (86%) | 3,850 (93%) | 2,836 (81%) | 80 (33%) |
| Follicular cancer T-96 M-83303 | 395 (5.0%) | 91 (2.2%) | 294 (8.4%) | 10 (4.1%) |
| Medullary cancer T-9605 M-85103 | 319 (4.0%) | 154 (3.7%) | 158 (4.5%) | 7 (2.9%) |
| Hürtle cell (oxyphilic) carcinoma T-96 M-82903 | 183 (2.3%) | 37 (0.9%) | 144 (4.1%) | 2 (0.8%) |
| Non-invasive follicular thyroid neoplasm with papillary-like nuclear features (NIFTP) | 133 (1.7%) | 0 (0%) | 1 (<0.1%) | 132 (54%) |
| Other | 111 (1.4%) | 13 (0.3%) | 84 (2.4%) | 14 (5.7%) |
| 1 n (%) | ||||
df %>%
filter(
malignant == 1,
# !is.na(fna_indicated)
) %>%
select(
fna_indicated,
tnm_t1a
) %>%
mutate(
fna_indicated = factor(fna_indicated),
fna_indicated = fct_recode(fna_indicated, `recommended` = "1", `not recommended`="0"),
# tnm_t1a = fct_relevel(tnm_t1a, "T1a", "T1b-T4b"),
fna_indicated = fct_explicit_na(fna_indicated)
) %>%
tbl_summary(by=fna_indicated) %>%
add_overall() %>%
modify_caption(paste0("**Table. Microcarcinomas by EU-TIRADS FNA indications status<br>(n=",sum(df$malignant)," malignant thyroid tumors)**"))
| Characteristic | Overall, N = 7,9071 | not recommended, N = 4,1451 | recommended, N = 3,5171 | (Missing), N = 2451 |
|---|---|---|---|---|
| tnm_t1a | 3,420 (45%) | 3,325 (82%) | 42 (1.2%) | 53 (51%) |
| Unknown | 308 | 90 | 77 | 141 |
| 1 n (%) | ||||
df %>%
filter(
malignant == 1,
# !is.na(fna_indicated)
) %>%
select(
fna_indicated,
high_risk_profile,
indeterminate_risk_profile,
low_risk_profile
) %>%
mutate(
fna_indicated = factor(fna_indicated),
fna_indicated = fct_recode(fna_indicated, `recommended` = "1", `not recommended`="0"),
# tnm_t1a = fct_relevel(tnm_t1a, "T1a", "T1b-T4b"),
fna_indicated = fct_explicit_na(fna_indicated)
) %>%
tbl_summary(by=fna_indicated) %>%
add_overall() %>%
modify_caption(paste0("**Table. thyroid cancer risk profile by EU-TIRADS FNA indications status<br>(n=",sum(df$malignant)," malignant thyroid tumors)**"))
| Characteristic | Overall, N = 7,9071 | not recommended, N = 4,1451 | recommended, N = 3,5171 | (Missing), N = 2451 |
|---|---|---|---|---|
| high_risk_profile | 1,003 (13%) | 235 (5.7%) | 750 (21%) | 18 (7.3%) |
| indeterminate_risk_profile | 4,297 (54%) | 1,369 (33%) | 2,743 (78%) | 185 (76%) |
| low_risk_profile | 2,607 (33%) | 2,541 (61%) | 24 (0.7%) | 42 (17%) |
| 1 n (%) | ||||
high_risk_profile = any of the following: pT3b or higher, pN1b, pM1
low_risk_profile = papillary thyroid cancer AND pT1a AND (pN0 or pNX) AND (pM0 or pMX)
indeterminate_risk_profile = neither high_risk_profile nor low_risk_profile
df %>%
filter(malignant == 1) %>%
select(
tnm_t1a,
eutirads
) %>%
mutate(
tnm_t1a = factor(tnm_t1a),
tnm_t1a = fct_recode(tnm_t1a, `T1a` = "1", `T1b-T4b`="0"),
tnm_t1a = fct_relevel(tnm_t1a, "T1a", "T1b-T4b"),
tnm_t1a = fct_explicit_na(tnm_t1a)
)%>%
tbl_summary(by=tnm_t1a,
label = list(eutirads ~ "EU-TIRADS category")) %>%
modify_caption(paste0("**Table. EU-TIRADS categories in T1a vs higher T <br>(n=",sum(df$malignant)," malignant thyroid tumors)**"))
| Characteristic | T1a, N = 3,4201 | T1b-T4b, N = 4,1791 | (Missing), N = 3081 |
|---|---|---|---|
| EU-TIRADS category | |||
| TR1 | 58 (1.7%) | 29 (0.7%) | 6 (1.9%) |
| TR2 | 143 (4.2%) | 79 (1.9%) | 21 (6.8%) |
| TR3 | 362 (11%) | 367 (8.8%) | 67 (22%) |
| TR4 | 934 (27%) | 1,242 (30%) | 83 (27%) |
| TR5 | 1,923 (56%) | 2,462 (59%) | 131 (43%) |
| 1 n (%) | |||
Pojawiła się sugestia, że z performance TIRADS może zależeć od ilości mikroraków.
Zmienna tnm_t1a_prop to proporcja nowotw. złośliwych o wym. =< 1 cm w materiale danego ośrodka.
# A tibble: 1 × 5
n_sites n_oper tnm_t1a_prop_p50 tnm_t1a_prop_min tnm_t1a_prop_max
<int> <int> <dbl> <dbl> <dbl>
1 37 18944 0.4 0.132 0.75
ggplot(site_perf_75, aes(x=tnm_t1a_prop, y=AUC)) +
geom_point() +
geom_smooth(method="lm", se=F) +
ylab("EU-TIRADS AUC-ROC") +
xlab("Proportion of pT1a malignancies")

na tym wykresie każda kropka to 1 ośrodek, na osi X proporcja raków pT1a w danym ośrodku, na osi Y wartość AUC ROC dla skali TIRADS