我有一个 Microsoft SQL Server 数据库,已通过 ODBC 链接连接到该数据库。我正在查询的表中有三列感兴趣:
serovar
、reportyear
和hospitalised
。
每一行代表一种情况。我想将一个聚合汇总表收集到 R 中,其中每行是一个血清型,每列是一个报告年份,每个单元格值代表住院百分比。
例如,如果 2020 年总共有 3 例 x 血清型病例,只有 2 例住院,我预计单元格中的百分比值为 66.67%。
这是一些示例数据:
# Create example data:
dt <- data.frame(
pid = c(1,2,3,4,5,6,7,8),
serovar = c("x", "x", "x", "x", "y", "y", "y", "y"),
reportyear = c(2020, 2020, 2020, 2021, 2020, 2020, 2021, 2021),
hospitalised = c("Y", "Y", "N", "N", NA, "Y", NA, "N"))
# Output:
> dt
pid serovar reportyear hospitalised
1 1 x 2020 Y
2 2 x 2020 Y
3 3 x 2020 N
4 4 x 2021 N
5 5 y 2020 <NA>
6 6 y 2020 Y
7 7 y 2021 <NA>
8 8 y 2021 N
我对数据库的惰性连接称为
db
:
# Create lazy connection to database:
db <- tbl(con,
category = params$category,
schema = "clean",
table = viewname)
然后我选择相关列,过滤数据并计算住院百分比:
summarytab <- db %>%
# Select required columns:
select(serovar, reportyear, hospitalised) %>%
# Re-code hospital:
mutate(hospitalised_tf = case_when(
hospitalised == "Y" ~ TRUE,
hospitalised == "N" ~ FALSE,
.default = NA
)) %>%
# Filter rows by year:
filter(between(reportyear, 2020, 2024)
& !is.na(serovar)
& !is.na(reportyear)
& !is.na(hospitalised_tf)) %>%
# Group by serovar and year:
group_by(serovar, reportyear) %>%
# Calculate total cases and number hospitalised by serovar and year:
summarise(total_cases = n(),
hosp_sum = sum(hospitalised_tf, na.rm = TRUE)) %>%
# Calculate percentage hospitalised:
mutate(hosp_pct = (hosp_sum/total_cases)*100) %>%
# Arrange by year and serovar:
arrange(serovar, reportyear) %>%
# Collect to check results:
collect()
如果我在 R 中的普通 data.frame 上运行此代码,我会得到预期的结果:
> summarytab
# A tibble: 4 × 5
# Groups: serovar [2]
serovar reportyear total_cases hosp_sum hosp_pct
<chr> <dbl> <int> <int> <dbl>
1 x 2020 3 2 66.7
2 x 2021 1 0 0
3 y 2020 1 1 100
4 y 2021 1 0 0
问题是实时数据库连接在百分比列中给出 0 或 100,而不是实际百分比。所以我假设有关 SQL 转换的某些内容是不正确的,但无法弄清楚是什么。 这是 SQL 查询:
show_query(summarytab)
<SQL>
SELECT "q01".*, ("hosp_sum" / "total_cases") * 100.0 AS "hosp_pct"
FROM (
SELECT
"serovar",
"reportyear",
COUNT(*) AS "total_cases",
SUM("hospitalised_tf") AS "hosp_sum"
FROM (
SELECT "q01".*
FROM (
SELECT
"q01".*,
CASE
WHEN ("hospitalised" = 'Y') THEN 1
WHEN ("hospitalised" = 'N') THEN 0
ELSE NULL
END AS "hospitalised_tf"
FROM (
SELECT
"Serotype" AS "serovar",
"DateUsedForStatisticsYear" AS "reportyear",
"Hospitalisation" AS "hospitalised"
FROM "FWD"."clean"."SALM_Case"
) "q01"
) "q01"
WHERE ("reportyear" BETWEEN 2020.0 AND 2024.0 AND NOT(("serovar" IS NULL)) AND NOT(("reportyear" IS NULL)) AND NOT(("hospitalised_tf" IS NULL)))
) "q01"
GROUP BY "serovar", "reportyear"
) "q01"
ORDER BY "serovar", "reportyear"
>
查询中似乎有很多重复,并且百分比计算位于顶部,这对我来说似乎很奇怪 - 但我看不出计算本身的编写方式有任何问题。
我将不胜感激,解释为什么在 R 环境中完成百分比计算时得到正确的结果,但当我尝试在服务器上通过 SQL 查询进行计算时得到错误的结果。
@siggemannen 的评论帮助我找到了解决方案 - 因此将其放在这里,以防有人遇到同样的困难。
问题的关键在于百分比计算的输入(列
hosp_sum
和total_cases
)都是整数,因此被存储为整数。 正如这篇 Stack Overflow 帖子中所解释的那样,存储为数字的数字可能比整数占用更多的空间,因为它们存储在两部分中(请参阅此处了解 R 中整数和数字之间的大小差异演示)。
SQL 旨在与数据库交互,因此优先考虑空间节省/计算时间而不是数学便利性。 因为我提供了两个整数作为百分比计算的输入,所以它默认提供一个整数作为结果 - 向上或向下舍入(因此是 0 和 100)。正如 @siggemannen 指出的,只需将两个输入数字之一从整数转换为数字就足以强制结果也存储为数字。我可以使用基本 R
dbplyr
函数通过 as.numeric()
执行此操作,然后将其直接传递到百分比计算,将其全部保留在 SQL 服务器上:
# Calculate total cases and number hospitalised by serovar and year:
summarise(total_cases = as.numeric(n()),
hosp_sum = sum(hospitalised_tf, na.rm = TRUE)) %>%
# Calculate percentage hospitalised:
mutate(hosp_pct = round((hosp_sum/total_cases)*100, digits = 2)) %>%
# Arrange by year and serovar:
arrange(reportyear, serovar) %>%
# Select columns:
select(serovar, reportyear, hosp_pct) %>%
# Collect the resultant table in a data.frame:
collect()
这给了我正确的数字结果(即 2/3 例住院 = 66.67%)。
另一方面,R 优先考虑灵活性和数字准确性,这就是为什么它会将计算的小数结果显示为数字,即使两个输入都存储为整数。正如this dbplyr vignette 中所解释的:
R 和 SQL 对于整数和实数有不同的默认值。 R中,1为实数,1L为整数。在 SQL 中,1 是整数,1.0 是实数同样在 R 中,整数是“数字的特殊情况”,而在 SQL 中,整数和实数(SQL 中表示数字)是完全独立的数据类型。