Module:Medical cases data
Appearance
This module is rated as alpha. It is ready for third-party input, and may be used on a few pages to see if problems arise, but should be watched. Suggestions for new features or changes in their input and output mechanisms are welcome. |
This module inserts charts and maps of medical cases related to a pandemic, broken down by subregions.
caseTable
[edit]Inserts a table of cases and deaths by region.
Usage: {{#invoke:Medical cases data|caseTable|config=Configuration}}
{{#invoke:Medical cases data|caseTable
|config=San Francisco Bay Area
}}
Counties | Cases[a] | Recov.[b] | Deaths | Pop. (2019) | C/1M | Ref. |
---|---|---|---|---|---|---|
9 | 1,937,165 | 11,216 | 7,739,378 | 250,300 | ||
Santa Clara | 527,681 | ? | 3,222 | 1,927,852 | 273,714 | d c[1] |
Alameda[c] | 400,532 | ? | 2,484 | 1,671,329 | 239,649 | d c[2] |
Contra Costa | 302,824 | 300,666 | 1,601 | 1,153,526 | 262,520 | d c[3] |
San Francisco | 203,788 | ? | 1,373 | 881,549 | 231,170 | d c[4] |
San Mateo | 184,001 | ? | 946 | 766,573 | 240,031 | d c[5] |
Sonoma | 119,749 | 118,903 | 668 | 494,336 | 242,242 | d c[6] |
Solano | 118,904 | 118,073 | 441 | 447,643 | 265,622 | d c[7] |
Marin | 44,650 | 7,030 | 274 | 258,826 | 172,510 | d c[8] |
Napa | 35,036 | 27,672 | 207 | 137,744 | 254,356 | d c[9] |
|
map
[edit]Inserts an interactive map of cases and deaths by region.
Usage: {{#invoke:Medical cases data|map|config=Configuration|frameWidth=Frame width in pixels|frameHeight=Frame height in pixels|caption=Caption as wikitext}}
{{#invoke:Medical cases data|map
|config=San Francisco Bay Area
|frameHeight=300
}}
Configuration
[edit]Run this Wikidata Query Service query to obtain a list of localized outbreaks to add to the regions
property in the table.
return {
caption = "Cases by county", -- Table or map caption as wikitext
outbreakItem = "Q94050008", -- QID of the Wikidata item representing the
regionTerm = "Counties", -- Term for each region, used as the first column's header
regionNamePattern = "(.+) County", -- Naming pattern for regions, applied to each region's item's label to display a short name in first column; see [[mw:Extension:Scribunto/Lua reference manual#Patterns]]
populationDate = "2020-01-01", -- Date of the population figures used to calculate per-capita infection rates
regions = {
-- Add a table for each region's outbreak
{
-- QID of the outbreak
entity = "Q94259368",
-- If there is tabular data for the outbreak, specify any column names that differ from the defaults
columns = {cases = "AC_CumulCases", deaths = "AC_CumulDeaths"},
-- Optional footnote for the region as wikitext
note = "Including cases in the City of Berkeley, which are reported by the Berkeley Public Health Division.",
},
},
columnNotes = {
cases = "Cumulative cases reported by each county's health department.", -- (Optional) Footnote for the "Cases" column as wikitext
recoveries = "Counties differ in what they consider to be a recovery.", -- (Optional) Footnote for the "Recoveries" column as wikitext
deaths = "Includes suspected cases.", -- (Optional) Footnote for the "Deaths" column as wikitext
}
}
Available configurations
[edit]
- ^ “Respiratory virus data”, “COVID cases and deaths dashboard”, “COVID-19 hospitalizations by date”, “COVID-19 Hospitalizations Dashboard”, “Count of deaths with COVID-19 by date”, “Deaths with COVID-19 by age group”, Santa Clara County Public Health. November 29, 2024.
- ^ Alameda County Data Sharing Initiative, Alameda County Public Health Department. November 23, 2024.
- ^ Contra Costa Health Services. May 22, 2023.
- ^ “ARCHIVED: COVID-19 Cases by Geography Over Time”, “COVID-19 Deaths Over Time”, “COVID-19 Hospitalizations”, DataSF, San Francisco Department of Public Health. November 19, 2024.
- ^ San Mateo County Health. April 27, 2023.
- ^ Case and Testing Data, Hospitalizations and Deaths, County of Sonoma. November 29, 2024.
- ^ Solano County Public Health. March 2, 2023.
- ^ “COVID-19 Case Disposition”, “COVID-19 Cumulative Demographics”, “Total Cases and Recovered by Date Reported among Marin County Community Residents”, “Total Hospitalizations and Deaths by Event Date among Marin County Community Residents”, Marin Health & Human Services. February 14, 2023.
- ^ “COVID-19 in Napa County”, “Landing Page”, “Situation Update Archive”, Napa County Health & Human Services Agency. May 14, 2023.
-- Usage: =p._caseTable({config="San Francisco Bay Area"})
local p = {}
local lang = mw.getContentLanguage()
local tabularData = require("Module:Tabular data")
local wd = require("Module:wd")
local mapFrame = require("Module:Mapframe")
local propertyIDsByDisposition = {
-- tests = "P8011",
cases = "P1603",
-- hospitalizations = "P8049",
recoveries = "P8010",
deaths = "P1120",
}
local function round(x)
return (math.modf(x + (x < 0 and -0.5 or 0.5)))
end
function p.pointInTime(statement)
local qualifiers = statement.qualifiers and statement.qualifiers.P585
local time = qualifiers and qualifiers[1].datavalue.value.time
return time and tonumber(lang:formatDate("U", time))
end
-- =tonumber(p.mostRecentStatement("Q83873577", "P1120").mainsnak.datavalue.value.amount)
function p.mostRecentStatement(entityID, propertyID, startDate, endDate)
local startTime = startDate and tonumber(lang:formatDate("U", startDate)) or -math.huge
local endTime = endDate and tonumber(lang:formatDate("U", endDate)) or math.huge
local statements = mw.wikibase.getBestStatements(entityID, propertyID)
local latestTime = -math.huge
local latestStatement
for i, statement in ipairs(statements) do
local time = p.pointInTime(statement)
if time and time > startTime and time < endTime and time > latestTime then
latestTime = time
latestStatement = statement
end
end
return latestStatement
end
function p.statementReference(statement)
local reference = statement.references and statement.references[1]
local referenceSnak = reference and reference.snaks.P248 and reference.snaks.P248[1]
local declarationQID = referenceSnak and referenceSnak.datavalue.value.id
if not declarationQID then
return nil
end
local name = mw.wikibase.formatValue(referenceSnak)
local url = mw.wikibase.getBestStatements(declarationQID, "P856")[1].mainsnak.datavalue.value
return {
name = declarationQID,
wikitext = url and mw.ustring.format("[%s %s]", url, name) or name,
}
end
function p._regionData(regionConfigs, populationDate, ignoredSources)
local regions = {}
for i, regionConfig in ipairs(regionConfigs) do
local outbreakEntity = regionConfig.entity
local locationEntity = mw.wikibase.getBestStatements(outbreakEntity, "P276")[1].mainsnak.datavalue.value.id
local dataTableStatement = mw.wikibase.getBestStatements(outbreakEntity, "P8204")[1]
local dataTableName
local dataTable
if dataTableStatement then
local qualifiers = dataTableStatement and dataTableStatement.qualifiers
local source = qualifiers and qualifiers.P1433 and qualifiers.P1433[1].datavalue.value.id
local ignored = false
for i, ignoredSource in ipairs(ignoredSources or {}) do
if source == ignoredSource then
ignored = true
break
end
end
if not ignored then
dataTableName = dataTableStatement.mainsnak.datavalue.value
dataTable = mw.ext.data.get((dataTableName:gsub("^Data:", "")))
end
end
local region = {
outbreakEntity = outbreakEntity,
locationEntity = locationEntity,
name = mw.wikibase.getLabel(locationEntity),
link = mw.wikibase.getSitelink(locationEntity),
population = tonumber(wd._property({
"raw",
locationEntity,
"P1082",
P585 = populationDate,
})),
dataTableName = dataTableName,
note = regionConfig.note,
sources = {},
}
local columns = regionConfig.columns
local latestTableDate = dataTable and tabularData._cell({
data = dataTable,
output_row = -1,
output_column = columns and (columns.date or columns.P585_date) or "date",
})
local latestTableTime = latestTableDate and tonumber(lang:formatDate("U", latestTableDate))
local usesDataTable = false
local casesStatement = p.mostRecentStatement(outbreakEntity, propertyIDsByDisposition.cases)
local casesTime = casesStatement and p.pointInTime(casesStatement)
if casesTime and (not latestTableTime or casesTime > latestTableTime) then
region.cases = tonumber(casesStatement.mainsnak.datavalue.value.amount)
local reference = p.statementReference(casesStatement)
if reference then
region.sources[reference.name] = reference.wikitext
end
elseif latestTableTime then
region.cases = dataTable and (tabularData._cell({
data = dataTable,
output_row = -1,
output_column = columns and columns.cases or "totalConfirmedCases",
}) or tabularData._lookup({
data = dataTable,
search_pattern = "%d",
search_column = columns and columns.cases or "totalConfirmedCases",
occurrence = -1,
output_column = columns and columns.cases or "totalConfirmedCases",
})) + (columns and columns.cases2 and tabularData._cell({
data = dataTable,
output_row = -1,
output_column = columns.cases2,
}) or 0)
usesDataTable = true
end
region.arrivalDate = dataTable and tabularData._lookup({
data = dataTable,
search_pattern = "[1-9]",
search_column = columns and columns.cases or "totalConfirmedCases",
occurrence = 1,
output_column = columns and (columns.date or columns.P585_date) or "date",
})
local deathsStatement = p.mostRecentStatement(outbreakEntity, propertyIDsByDisposition.deaths)
local deathsTime = deathsStatement and p.pointInTime(deathsStatement)
if deathsTime and (not latestTableTime or deathsTime > latestTableTime) then
region.deaths = tonumber(deathsStatement.mainsnak.datavalue.value.amount)
local reference = p.statementReference(deathsStatement)
if reference then
region.sources[reference.name] = reference.wikitext
end
elseif latestTableTime then
region.deaths = dataTable and (tabularData._cell({
data = dataTable,
output_row = -1,
output_column = columns and columns.deaths or "deaths",
}) or tabularData._lookup({
data = dataTable,
search_pattern = "%d",
search_column = columns and columns.deaths or "deaths",
occurrence = -1,
output_column = columns and columns.deaths or "deaths",
}))
usesDataTable = true
end
local recoveriesStatement = p.mostRecentStatement(outbreakEntity, propertyIDsByDisposition.recoveries)
local recoveriesTime = recoveriesStatement and p.pointInTime(recoveriesStatement)
if recoveriesTime and (not latestTableTime or recoveriesTime > latestTableTime) then
region.recoveries = tonumber(recoveriesStatement.mainsnak.datavalue.value.amount)
local reference = p.statementReference(recoveriesStatement)
if reference then
region.sources[reference.name] = reference.wikitext
end
elseif latestTableTime then
region.recoveries = columns and columns.recoveries and dataTable and (tabularData._cell({
data = dataTable,
output_row = -1,
output_column = columns.recoveries,
}) or tabularData._lookup({
data = dataTable,
search_pattern = "%d",
search_column = columns.recoveries,
occurrence = -1,
output_column = columns.recoveries,
}))
usesDataTable = true
end
local viewLinks = {
mw.ustring.format("[[d:%s|d]]", region.outbreakEntity),
}
if dataTableName then
table.insert(viewLinks, mw.ustring.format("[[c:%s|c]]", dataTableName))
end
region.viewLink = table.concat(viewLinks, " ")
if usesDataTable then
local formattedDate = latestTableTime
local reference = mw.ustring.format("%s. %s.", dataTable.sources:gsub("<br */?>.*", ""), lang:formatDate("F j, Y", latestTableDate))
region.sources[dataTableName] = reference
end
table.insert(regions, region)
end
return regions
end
local function addNumericCell(row, contents)
if contents then
row
:tag("td")
:attr("align", "right")
:attr("data-sort-value", contents)
:wikitext(lang:formatNum(contents))
else
row
:tag("td")
:addClass("unknown")
:addClass("table-unknown")
:attr("align", "center")
:css({
background = "#ececec",
color = "#2c2c2c",
["font-size"] = "smaller",
["vertical-align"] = "middle",
})
:attr("data-sort-value", "0")
:wikitext("?")
end
return row
end
-- Usage: =p._caseTable({config="San Francisco Bay Area"})
function p._caseTable(args)
local frame = mw.getCurrentFrame()
local config = args.config and mw.loadData("Module:Medical cases data/" .. args.config)
local populationDate = config and config.populationDate or args.populationDate
local regions = p._regionData(
config and config.regions,
populationDate,
config and config.ignoredSources)
table.sort(regions, function (left, right)
local leftCases = left.cases or 0
local rightCases = right.cases or 0
return leftCases == rightCases and left.name < right.name or leftCases > rightCases
end)
local totals = {
regions = #regions,
cases = 0,
deaths = 0,
recoveries = 0,
population = 0,
}
for i, region in ipairs(regions) do
totals.cases = totals.cases + (region.cases or 0)
totals.deaths = totals.deaths + (region.deaths or 0)
totals.recoveries = totals.recoveries and region.recoveries and (totals.recoveries + region.recoveries)
totals.population = totals.population + (region.population or 0)
end
local htmlTable = mw.html.create("table")
:addClass("wikitable")
:addClass("sortable")
:addClass("plainrowheaders")
:attr("align", "right")
:css({
["font-size"] = "85%",
})
htmlTable
:tag("caption")
:wikitext(config and config.caption or args.caption)
local headerRow = htmlTable
:tag("tr")
local totalRow = htmlTable
:tag("tr")
local columnNotes = config and config.columnNotes
headerRow
:tag("th")
:attr("scope", "col")
:attr("data-sort-type", "text")
:wikitext(config and config.regionTerm or args.regionTerm or "Regions")
:wikitext(columnNotes and columnNotes.regions and frame:expandTemplate {
title = "efn",
args = {
columnNotes.regions
}
})
totalRow
:tag("th")
:attr("align", "right")
:wikitext(lang:formatNum(totals.regions))
headerRow
:tag("th")
:attr("scope", "col")
:attr("data-sort-type", "number")
:wikitext("Cases")
:wikitext(columnNotes and columnNotes.cases and frame:expandTemplate {
title = "efn",
args = {
columnNotes.cases
}
})
totalRow
:tag("th")
:attr("align", "right")
:attr("data-sort-type", "number")
:wikitext(lang:formatNum(totals.cases))
local recoveriesHeader = headerRow
:tag("th")
:attr("scope", "col")
:attr("data-sort-type", "number")
recoveriesHeader
:tag("abbr")
:attr("title", "Recoveries")
:wikitext("Recov.")
recoveriesHeader
:wikitext(columnNotes and columnNotes.recoveries and frame:expandTemplate {
title = "efn",
args = {
columnNotes.recoveries
}
})
totalRow
:tag("th")
:attr("align", "right")
:wikitext(totals.recoveries and lang:formatNum(totals.recoveries))
headerRow
:tag("th")
:attr("scope", "col")
:attr("data-sort-type", "number")
:wikitext("Deaths")
:wikitext(columnNotes and columnNotes.deaths and frame:expandTemplate {
title = "efn",
args = {
columnNotes.deaths
}
})
totalRow
:tag("th")
:attr("align", "right")
:attr("data-sort-type", "number")
:wikitext(lang:formatNum(totals.deaths))
local populationHeader = headerRow
:tag("th")
:attr("scope", "col")
:attr("data-sort-type", "number")
populationHeader
:tag("abbr")
:attr("title", "Population")
:wikitext("Pop.")
if populationDate then
populationHeader
:wikitext(mw.ustring.format(" (%d)", lang:formatDate("Y", populationDate)))
end
populationHeader
:wikitext(columnNotes and columnNotes.population and frame:expandTemplate {
title = "efn",
args = {
columnNotes.population
}
})
totalRow
:tag("th")
:attr("align", "right")
:attr("data-sort-type", "number")
:wikitext(lang:formatNum(totals.population))
headerRow
:tag("th")
:attr("scope", "col")
:attr("data-sort-type", "number")
:tag("abbr")
:attr("title", "Cases per 1 million inhabitants")
:wikitext("C/1M")
:wikitext(columnNotes and columnNotes.casesPerMillion and frame:expandTemplate {
title = "efn",
args = {
columnNotes.casesPerMillion
}
})
totalRow
:tag("th")
:attr("align", "right")
:attr("data-sort-type", "number")
:wikitext(lang:formatNum(round(totals.cases / totals.population * 1e6)))
headerRow
:tag("th")
:attr("scope", "col")
:attr("rowspan", 2)
:addClass("unsortable")
:tag("abbr")
:attr("title", "Reference")
:wikitext("Ref.")
local regionNamePattern = config and config.regionNamePattern or args.regionNamePattern
for i, region in ipairs(regions) do
local row = htmlTable:tag("tr")
local name = region.name
if regionNamePattern then
name = mw.ustring.match(region.name, regionNamePattern) or name
end
row
:tag("th")
:attr("scope", "row")
:wikitext(mw.ustring.format("[[%s|%s]]", region.link, name))
:wikitext(region.note and frame:expandTemplate {
title = "efn",
args = {
region.note,
}
})
addNumericCell(row, region.cases)
addNumericCell(row, region.recoveries)
addNumericCell(row, region.deaths)
addNumericCell(row, region.population)
addNumericCell(row, region.cases and region.population and round(region.cases / region.population * 1e6))
local refCell = row
:tag("td")
:attr("align", "center")
:wikitext(region.viewLink)
for name, wikitext in pairs(region.sources) do
refCell:wikitext(frame:callParserFunction {
name = "#tag:ref",
args = {
name = name,
wikitext,
},
})
end
end
local footerRow = htmlTable
:tag("tr")
:addClass("sortbottom")
footerRow
:tag("td")
:attr("colspan", 7)
:attr("align", "left")
:css({
width = 0,
})
:wikitext(frame:expandTemplate {
title = "notelist",
})
return htmlTable
end
function p.caseTable(frame)
return p._caseTable(frame.args)
end
function p._statistics(args)
local frame = mw.getCurrentFrame()
local config = args.config and mw.loadData("Module:Medical cases data/" .. args.config)
local populationDate = config and config.populationDate or args.populationDate
local regions = p._regionData(
config and config.regions,
populationDate,
config and config.ignoredSources)
local stats = {
regions = #regions,
cases = 0,
deaths = 0,
recoveries = 0,
recoveriesRegions = 0,
population = 0,
}
for i, region in ipairs(regions) do
stats.cases = stats.cases + (region.cases or 0)
stats.deaths = stats.deaths + (region.deaths or 0)
if region.recoveries then
stats.recoveries = stats.recoveries + region.recoveries
stats.recoveriesRegions = stats.recoveriesRegions + 1
end
stats.population = stats.population + (region.population or 0)
if not stats.arrivalDate or region.arrivalDate < stats.arrivalDate then
stats.arrivalDate = region.arrivalDate
end
end
return stats
end
function p.statistics(frame)
return p._statistics(frame.args)[mw.text.trim(frame.args[1])]
end
local function fillColor(casesPerCapita)
-- [[c:Template:COVID-19 Prevalence in US by county]]
local percent = casesPerCapita * 100
if percent >= 10.00 then return "#510000" end
if percent >= 3.00 then return "#99000d" end
if percent >= 1.00 then return "#cb181d" end
if percent >= 0.30 then return "#fb6a4a" end
if percent >= 0.10 then return "#fc9272" end
if percent >= 0.03 then return "#fcbba1" end
if percent >= 0.00 then return "#fee5d9" end
return "#cccccc"
end
-- Usage: =p._map({config="San Francisco Bay Area"})
function p._map(args)
local frame = mw.getCurrentFrame()
local config = args.config and mw.loadData("Module:Medical cases data/" .. args.config)
local populationDate = config and config.populationDate or args.populationDate
local regions = p._regionData(
config and config.regions,
populationDate,
config and config.ignoredSources)
local params = {
frame = "yes",
["frame-width"] = args.frameWidth or (config and config.frameWidth),
["frame-height"] = args.frameHeight or (config and config.frameHeight),
text = args.caption or (config and config.caption),
}
for i, region in ipairs(regions) do
i = i == 1 and "" or i
params["type" .. i] = "shape"
params["id" .. i] = region.locationEntity
params["title" .. i] = region.name
params["stroke-color" .. i] = "#ffffff"
params["stroke-width" .. i] = 1
params["fill" .. i] = fillColor(region.cases / region.population)
local details = {
mw.ustring.format("%s cases (%s/1M)", lang:formatNum(region.cases),
lang:formatNum(round(region.cases / region.population * 1e6))),
mw.ustring.format("%s deaths", lang:formatNum(region.deaths)),
}
if region.recoveries then
table.insert(details, mw.ustring.format("%s recoveries", lang:formatNum(region.recoveries)))
end
params["description" .. i] = table.concat(details, "<br>")
end
return frame:preprocess(mapFrame._main(params))
end
function p.map(frame)
return p._map(frame.args)
end
return p