SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
26.8 KB · 447 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { Download } from 'lucide-react';4import { PageHeader, Section, Note } from '@/components/ui/section';5import { Badge, ClaimBadge } from '@/components/ui/badge';6import { EmptyState } from '@/components/ui/empty-state';7import { Freshness } from '@/components/ui/freshness';8import { SourceBadge } from '@/components/ui/source-badge';9import { JsonView } from '@/components/ui/json-view';10import { ScatterChart, type ScatterPoint } from '@/components/charts/scatter-chart';11import { listGapScopes, pickGapScope, gapComponents, gapSums, gapScopeKey, gapMetricDefs, type GapComponent } from '@/lib/queries/research-gap';12import { fmtInt, fmtNum, fmtPct, fmtValue, fmtDateTime, humanize } from '@/lib/format';13import { str, int, oneOf, withParams, type SP } from '@/lib/search-params';1415export const dynamic = 'force-dynamic';1617export const metadata: Metadata = {18  title: 'Research Gap Index — burden vs research activity',19  description: 'Deaths, active trials and publications per top-level cancer in one burden scope; shares, log2 gap ratios and per-1,000-deaths intensities with their formula versions and inputs.',20};2122const SORT_KEYS = ['canonical_name', 'deaths', 'death_share', 'active_trials', 'trial_share', 'publications_5y', 'publication_share', 'trial_gap_ratio', 'research_gap_ratio', 'trials_per_1000_deaths', 'publications_per_1000_deaths'] as const;23type SortKey = (typeof SORT_KEYS)[number];2425function sortRows(rows: GapComponent[], key: SortKey, dir: 'asc' | 'desc'): GapComponent[] {26  const sgn = dir === 'asc' ? 1 : -1;27  return [...rows].sort((a, b) => {28    // Ineligible rows always sink to the bottom.29    if (a.eligible !== b.eligible) return a.eligible ? -1 : 1;30    if (key === 'canonical_name') return sgn * a.canonical_name.localeCompare(b.canonical_name);31    const av = a[key];32    const bv = b[key];33    if (av == null && bv == null) return a.canonical_name.localeCompare(b.canonical_name);34    if (av == null) return 1;35    if (bv == null) return -1;36    return sgn * (Number(av) - Number(bv)) || a.canonical_name.localeCompare(b.canonical_name);37  });38}3940/** Short display label for chart points: the entity's short name, else the canonical name without the "Malignant … Neoplasm" wrapper. */41function chartLabel(r: { canonical_name: string; short_name: string | null }): string {42  if (r.short_name) return r.short_name;43  return r.canonical_name.replace(/^Malignant\s+/i, '').replace(/\s+Neoplasm$/i, '');44}4546function Ratio({ v }: { v: number | null }) {47  if (v == null) return <span className="text-ink-4">—</span>;48  const cls = v >= 1 ? 'text-danger font-medium' : v > 0 ? 'text-ink' : 'text-ink-3';49  return <span className={cls}>{fmtValue(v, 'log2_ratio')}</span>;50}5152export default async function ResearchGapPage({ searchParams }: { searchParams: Promise<SP> }) {53  const sp = await searchParams;54  const scopes = await listGapScopes();55  const want = { geography: str(sp, 'geography', 'USA'), year: int(sp, 'year', 0, 1900, 2100) || null, sex: oneOf(sp, 'sex', ['all', 'male', 'female'] as const, 'all'), source: str(sp, 'source') };56  const scope = pickGapScope(scopes, want);57  const sort = oneOf(sp, 'sort', SORT_KEYS, 'research_gap_ratio');58  const dir = oneOf(sp, 'dir', ['asc', 'desc'] as const, sort === 'canonical_name' ? 'asc' : 'desc');59  const [rowsRaw, defs] = scope ? await Promise.all([gapComponents(scope), gapMetricDefs()]) : [[] as GapComponent[], new Map()];60  const rows = sortRows(rowsRaw, sort, dir);61  const eligible = rows.filter((r) => r.eligible);62  const sums = gapSums(rows);63  const current = { geography: scope?.geography ?? want.geography, year: scope?.year ?? null, sex: scope?.sex ?? want.sex, source: scope?.source_slug ?? null, sort, dir };64  const sortLink = (key: SortKey) => withParams(current, { sort: key, dir: sort === key ? (dir === 'desc' ? 'asc' : 'desc') : key === 'canonical_name' ? 'asc' : 'desc' });65  const sortMark = (key: SortKey) => (sort === key ? (dir === 'desc' ? ' ▼' : ' ▲') : '');66  const geographies = [...new Set(scopes.map((s) => s.geography))];67  const yearsFor = (geo: string, sex: string) => [...new Set(scopes.filter((s) => s.geography === geo && s.sex === sex).map((s) => Number(s.year)))].sort((a, b) => b - a);68  const sexesFor = (geo: string) => [...new Set(scopes.filter((s) => s.geography === geo).map((s) => s.sex))];69  const sourcesFor = (geo: string, year: number | null, sex: string) => scopes.filter((s) => s.geography === geo && Number(s.year) === year && s.sex === sex);70  const pubSlope = sums.deaths > 0 ? sums.publications5y / sums.deaths : 0;71  const trialSlope = sums.deaths > 0 ? sums.activeTrials / sums.deaths : 0;72  const cancerHref = (r: GapComponent) => `/cancer/${r.slug}/rankings`;73  const sourceBadge = scope ? <SourceBadge p={{ sourceSlug: scope.source_slug, sourceName: scope.source_name, dataset: `mortality_count · ${scope.geography} · ${scope.year} · ${scope.sex}`, layer: 'normalized' }} compact /> : null;74  const pubPoints: ScatterPoint[] = eligible.map((r) => ({75    id: r.cancer_id,76    label: chartLabel(r),77    href: cancerHref(r),78    x: Number(r.deaths),79    y: Number(r.publications_5y),80    size: Number(r.active_trials),81    tooltip: `${r.canonical_name} — deaths ${fmtInt(r.deaths)} (${scope?.year}) · publications 5 y ${fmtInt(r.publications_5y)} · active trials ${fmtInt(r.active_trials)} · research gap ratio ${fmtValue(r.research_gap_ratio, 'log2_ratio')}`,82  }));83  const trialPoints: ScatterPoint[] = eligible.map((r) => ({84    id: r.cancer_id,85    label: chartLabel(r),86    href: cancerHref(r),87    x: Number(r.deaths),88    y: Number(r.active_trials),89    tooltip: `${r.canonical_name} — deaths ${fmtInt(r.deaths)} (${scope?.year}) · active trials ${fmtInt(r.active_trials)} · trial gap ratio ${fmtValue(r.trial_gap_ratio, 'log2_ratio')}`,90  }));91  const dTrial = defs.get('trial_gap_ratio');92  const dRes = defs.get('research_gap_ratio');93  const dTpd = defs.get('trials_per_1000_deaths');94  const dPpd = defs.get('publications_per_1000_deaths');9596  return (97    <div>98      <PageHeader99        kicker="Unmet need · computed index"100        title="Research Gap Index"101        lede="How a cancer's share of deaths compares with its share of registered research activity — active interventional trials (ClinicalTrials.gov) and publications of the last five years (PubMed) — within one burden scope. It measures where registered activity is thin relative to mortality; it does not measure the quality, funding or difficulty of research, and it inherits every limit of the burden source."102      >103        <p className="mt-2 flex flex-wrap items-center gap-2 text-[12.5px] text-ink-3">104          <ClaimBadge kind="computed" />105          {scope ? <span className="ci-mono">{scope.formula_version}</span> : null}106          {dTrial ? <span className="ci-mono">{dTrial.formula_version}</span> : null}107          {dRes ? <span className="ci-mono">{dRes.formula_version}</span> : null}108          <Link href="/methodology#research-gap" className="ci-link">109            Methodology110          </Link>111          <Link href="/rankings/trial_gap_ratio" className="ci-link">112            Trial Gap Ratio ranking113          </Link>114          <Link href="/rankings/research_gap_ratio" className="ci-link">115            Research Gap Ratio ranking116          </Link>117        </p>118        <Note>119          Burden is currently <strong>United States only</strong> (CDC WONDER and U.S. Cancer Statistics): global estimates (IARC / GLOBOCAN) remain under license review, so no world scope exists yet. Shares and ratios are relative to the eligible top-level cancers of the scope, not to all cancers.120        </Note>121      </PageHeader>122123      {!scope ? (124        <EmptyState title="Research gap components not yet computed" knows={[{ label: 'Percentile-based Trial Gap Index', href: '/rankings/trial_gap' }, { label: 'Percentile-based Research Gap Index', href: '/rankings/research_gap' }, { label: 'Methodology', href: '/methodology#research-gap' }]}>125          Components require a burden scope (annual deaths per top-level cancer for one geography, year, sex and source) plus trial and literature counters. They are recomputed by <code className="ci-mono">pnpm cix intel</code>.126        </EmptyState>127      ) : (128        <>129          {/* Scope selector (GET params, server-rendered) */}130          <form method="get" action="/research-gap" className="ci-rule flex flex-wrap items-end gap-3 pt-4 text-[13px]" aria-label="Burden scope">131            <label className="flex flex-col gap-0.5">132              <span className="ci-kicker">Geography</span>133              <select name="geography" defaultValue={scope.geography} className="border border-rule bg-paper px-2 py-1">134                {geographies.map((g) => (135                  <option key={g} value={g}>136                    {g}137                  </option>138                ))}139              </select>140            </label>141            <label className="flex flex-col gap-0.5">142              <span className="ci-kicker">Sex</span>143              <select name="sex" defaultValue={scope.sex} className="border border-rule bg-paper px-2 py-1">144                {sexesFor(scope.geography).map((s) => (145                  <option key={s} value={s}>146                    {s === 'all' ? 'both sexes' : humanize(s)}147                  </option>148                ))}149              </select>150            </label>151            <label className="flex flex-col gap-0.5">152              <span className="ci-kicker">Year</span>153              <select name="year" defaultValue={String(scope.year)} className="border border-rule bg-paper px-2 py-1">154                {yearsFor(scope.geography, scope.sex).map((y) => (155                  <option key={y} value={y}>156                    {y}157                  </option>158                ))}159              </select>160            </label>161            <label className="flex flex-col gap-0.5">162              <span className="ci-kicker">Burden source</span>163              <select name="source" defaultValue={scope.source_slug} className="border border-rule bg-paper px-2 py-1">164                {sourcesFor(scope.geography, Number(scope.year), scope.sex).map((s) => (165                  <option key={s.source_slug} value={s.source_slug}>166                    {s.source_slug} ({s.n_eligible} eligible)167                  </option>168                ))}169              </select>170            </label>171            <input type="hidden" name="sort" value={sort} />172            <input type="hidden" name="dir" value={dir} />173            <button type="submit" className="border border-rule-strong px-3 py-1 text-ink hover:border-accent hover:text-accent">174              Apply175            </button>176            <span className="text-[12px] text-ink-3">177              Scope key <span className="ci-mono">{gapScopeKey(scope)}</span> · burden source {sourceBadge}178            </span>179          </form>180181          {/* KPI strip */}182          <section aria-label="Scope totals" className="mt-5">183            <ul className="grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-5">184              {[185                { label: 'Scope', value: `${scope.geography} · ${scope.year}`, note: `${scope.sex === 'all' ? 'both sexes' : humanize(scope.sex)} · all ages · top-level cancers`, text: true },186                { label: 'Eligible cancers', value: fmtInt(sums.eligible), note: `of ${fmtInt(rows.length)} with a mortality observation · deaths ≥ 100` },187                { label: 'Σ deaths', value: fmtInt(sums.deaths), note: `${scope.source_slug} · ${scope.year} · eligible set` },188                { label: 'Σ active trials', value: fmtInt(sums.activeTrials), note: 'ClinicalTrials.gov · cancer + NCIt descendants' },189                { label: 'Σ publications (5 y)', value: fmtInt(sums.publications5y), note: 'PubMed · query-based per entity' },190              ].map((k) => (191                <li key={k.label}>192                  <span className={`block font-display text-ink ${k.text ? 'text-xl sm:text-2xl' : 'ci-num text-2xl sm:text-3xl'}`}>{k.value}</span>193                  <span className="block text-[12.5px] font-medium text-ink-2">{k.label}</span>194                  <span className="block text-[11.5px] text-ink-3">{k.note}</span>195                </li>196              ))}197            </ul>198            <Freshness dataUpdatedAt={scope.computed_at} extra={`components ${scope.formula_version} · sums are over the eligible set only`} />199          </section>200201          {/* Scatter 1 */}202          <Section203            id="deaths-vs-publications"204            kicker="Burden vs literature"205            title="Annual deaths vs publications of the last five years"206            description={`Each bubble is one eligible top-level cancer; bubble area is proportional to its active interventional trials. The dashed line is the scope's overall intensity (${fmtNum(pubSlope * 1000, 0)} publications per 1,000 deaths): cancers below it have a positive Research Gap Ratio — fewer publications than their share of deaths would suggest — and cancers above it a negative one.`}207            className="mt-8"208            actions={209              <Link href={`/rankings/research_gap_ratio?scope=${encodeURIComponent(gapScopeKey(scope))}`} className="ci-link">210                Research Gap Ratio ranking →211              </Link>212            }213          >214            {pubPoints.length ? (215              <ScatterChart points={pubPoints} xLabel={`Annual deaths, ${scope.year}`} yLabel="Publications, last 5 years" sizeLabel="Active trials" ariaLabel={`Scatter of annual deaths against publications for ${pubPoints.length} eligible top-level cancers, ${scope.geography} ${scope.year}`} reference={{ slope: pubSlope, label: `Scope average: ${fmtNum(pubSlope * 1000, 0)} publications per 1,000 deaths (Research Gap Ratio = 0)` }} width={880} height={460} />216            ) : (217              <EmptyState compact>No eligible cancer in this scope.</EmptyState>218            )}219            <p className="mt-1.5 flex flex-wrap items-center gap-2 text-[11.5px] text-ink-3">220              <ClaimBadge kind="computed" />221              <span>222                x: {sourceBadge} mortality_count · y: <Link href="/source/pubmed" className="ci-src">pubmed</Link> publications_5y · size: <Link href="/source/clinicaltrials" className="ci-src">clinicaltrials</Link> active_trials223              </span>224            </p>225          </Section>226227          {/* Scatter 2 */}228          <Section229            id="deaths-vs-trials"230            kicker="Burden vs clinical research"231            title="Annual deaths vs active interventional trials"232            description={`Dashed line: ${fmtNum(trialSlope * 1000, 1)} active trials per 1,000 deaths (Trial Gap Ratio = 0). Below the line: positive Trial Gap Ratio.`}233            className="mt-8"234            actions={235              <Link href={`/rankings/trial_gap_ratio?scope=${encodeURIComponent(gapScopeKey(scope))}`} className="ci-link">236                Trial Gap Ratio ranking →237              </Link>238            }239          >240            <div className="max-w-3xl">241              {trialPoints.length ? <ScatterChart points={trialPoints} xLabel={`Annual deaths, ${scope.year}`} yLabel="Active interventional trials" ariaLabel={`Scatter of annual deaths against active trials for ${trialPoints.length} eligible top-level cancers, ${scope.geography} ${scope.year}`} reference={{ slope: trialSlope, label: `Scope average: ${fmtNum(trialSlope * 1000, 1)} active trials per 1,000 deaths (Trial Gap Ratio = 0)` }} width={640} height={320} /> : <EmptyState compact>No eligible cancer in this scope.</EmptyState>}242            </div>243          </Section>244245          {/* Table */}246          <Section247            id="components"248            kicker="Components"249            title="Deaths, activity, shares and gap ratios per cancer"250            description="Click a column header to sort (the order is kept in the URL). Ineligible cancers are listed last with the reason. Shares are the cancer's fraction of the eligible set's total; ratios are log₂(death share ÷ activity share): 0 = proportional, +1 = twice the death share, −1 = half."251            className="mt-8"252            actions={253              <a href={`/api/export/research-gap.csv${withParams({ geography: scope.geography, year: scope.year, sex: scope.sex, source: scope.source_slug }, {})}`} className="inline-flex items-center gap-1.5 border border-rule px-2.5 py-1 text-[13px] text-ink no-underline hover:border-accent hover:text-accent">254                <Download className="h-3.5 w-3.5" aria-hidden /> CSV (with attribution)255              </a>256            }257          >258            <div className="mb-2 flex flex-wrap items-center gap-2 text-[12px] text-ink-3">259              {sourceBadge}260              <Link href="/source/clinicaltrials" className="ci-src">261                clinicaltrials262              </Link>263              <Link href="/source/pubmed" className="ci-src">264                pubmed265              </Link>266              <ClaimBadge kind="computed" />267              <span>268                {fmtInt(rows.length)} top-level cancers with a {scope.year} mortality observation · {fmtInt(sums.eligible)} eligible269              </span>270            </div>271            <div className="ci-table-wrap">272              <table className="ci-table">273                <thead>274                  <tr>275                    <th>276                      <Link href={sortLink('canonical_name')} className="no-underline">277                        Cancer{sortMark('canonical_name')}278                      </Link>279                    </th>280                    <th className="num">281                      <Link href={sortLink('deaths')} className="no-underline" title={`mortality_count · ${scope.source_slug} · ${scope.year}`}>282                        Deaths {scope.year}283                        {sortMark('deaths')}284                      </Link>285                    </th>286                    <th className="num">287                      <Link href={sortLink('death_share')} className="no-underline">288                        Death share{sortMark('death_share')}289                      </Link>290                    </th>291                    <th className="num">292                      <Link href={sortLink('active_trials')} className="no-underline">293                        Active trials{sortMark('active_trials')}294                      </Link>295                    </th>296                    <th className="num">297                      <Link href={sortLink('trial_share')} className="no-underline">298                        Trial share{sortMark('trial_share')}299                      </Link>300                    </th>301                    <th className="num">302                      <Link href={sortLink('publications_5y')} className="no-underline">303                        Publications 5 y{sortMark('publications_5y')}304                      </Link>305                    </th>306                    <th className="num">307                      <Link href={sortLink('publication_share')} className="no-underline">308                        Publication share{sortMark('publication_share')}309                      </Link>310                    </th>311                    <th className="num">312                      <Link href={sortLink('trial_gap_ratio')} className="no-underline" title={dTrial?.formula}>313                        Trial gap ratio{sortMark('trial_gap_ratio')}314                      </Link>315                    </th>316                    <th className="num">317                      <Link href={sortLink('research_gap_ratio')} className="no-underline" title={dRes?.formula}>318                        Research gap ratio{sortMark('research_gap_ratio')}319                      </Link>320                    </th>321                    <th className="num">322                      <Link href={sortLink('trials_per_1000_deaths')} className="no-underline" title={dTpd?.formula}>323                        Trials / 1,000 deaths{sortMark('trials_per_1000_deaths')}324                      </Link>325                    </th>326                    <th className="num">327                      <Link href={sortLink('publications_per_1000_deaths')} className="no-underline" title={dPpd?.formula}>328                        Pubs / 1,000 deaths{sortMark('publications_per_1000_deaths')}329                      </Link>330                    </th>331                    <th>Eligibility</th>332                    <th>Inputs</th>333                  </tr>334                </thead>335                <tbody>336                  {rows.map((r) => (337                    <tr key={r.cancer_id} className={r.eligible ? '' : 'text-ink-3'}>338                      <td className="min-w-[200px]">339                        <Link className="ci-link" href={cancerHref(r)}>340                          {r.canonical_name}341                        </Link>342                        {r.eligible && (r.trial_gap_rank != null || r.research_gap_rank != null) ? (343                          <span className="ml-1.5 text-[11px] text-ink-3" title="Rank in the Trial Gap Ratio / Research Gap Ratio rankings of this scope (1 = largest gap)">344                            #{r.trial_gap_rank ?? '—'} / #{r.research_gap_rank ?? '—'}345                          </span>346                        ) : null}347                      </td>348                      <td className="num">349                        <span className="ci-num">{fmtInt(r.deaths)}</span>350                        <span className="ml-1 text-[11px] text-ink-3">351                          {scope.year} {sourceBadge}352                        </span>353                      </td>354                      <td className="num">{fmtPct(r.death_share, 1)}</td>355                      <td className="num">{fmtInt(r.active_trials)}</td>356                      <td className="num">{fmtPct(r.trial_share, 1)}</td>357                      <td className="num">{fmtInt(r.publications_5y)}</td>358                      <td className="num">{fmtPct(r.publication_share, 1)}</td>359                      <td className="num">360                        <Ratio v={r.trial_gap_ratio} />361                      </td>362                      <td className="num">363                        <Ratio v={r.research_gap_ratio} />364                      </td>365                      <td className="num">{fmtValue(r.trials_per_1000_deaths, 'per_1000_deaths')}</td>366                      <td className="num">{fmtValue(r.publications_per_1000_deaths, 'per_1000_deaths')}</td>367                      <td>368                        {r.eligible ? (369                          <Badge tone="ok" title="deaths ≥ 100; ratios require ≥ 1 trial / ≥ 1 publication">370                            eligible371                          </Badge>372                        ) : (373                          <Badge tone="outline" title={r.ineligible_reason ?? undefined}>374                            {r.ineligible_reason?.split(' ')[0]?.replace(/_/g, ' ') ?? 'ineligible'}375                          </Badge>376                        )}377                      </td>378                      <td>379                        <details>380                          <summary className="ci-link text-[12.5px]">inputs</summary>381                          <div className="mt-1 max-w-[380px]">382                            <JsonView data={r.inputs} />383                            <p className="ci-mono mt-1 text-[10.5px] text-ink-4">384                              component {r.component_id} · {r.formula_version}385                            </p>386                          </div>387                        </details>388                      </td>389                    </tr>390                  ))}391                </tbody>392              </table>393            </div>394            <Freshness dataUpdatedAt={scope.computed_at} extra={`computed ${fmtDateTime(scope.computed_at)} · ${scope.formula_version}`} />395          </Section>396397          <div className="mt-8 grid gap-6 lg:grid-cols-[1fr_1fr]">398            <Section id="formulas" kicker="Formulas" title="How each column is computed" level={3}>399              {/* Stacked (not the two-column KV): formulas are long and must keep the full width on narrow screens. */}400              <dl className="space-y-2.5 text-[13px]">401                {[402                  { k: 'Death share', f: 'deaths / Σ deaths (eligible set)', v: null },403                  { k: 'Trial share', f: 'active_trials / Σ active_trials (eligible set)', v: null },404                  { k: 'Publication share', f: 'publications_5y / Σ publications_5y (eligible set)', v: null },405                  ...[dTrial, dRes, dTpd, dPpd].filter((d): d is NonNullable<typeof d> => !!d).map((d) => ({ k: d.name, f: d.formula, v: d.formula_version })),406                ].map((it) => (407                  <div key={it.k}>408                    <dt className="text-ink-3">{it.k}</dt>409                    <dd className="min-w-0 break-words">410                      <code className="ci-mono text-[12px]">{it.f}</code>411                      {it.v ? <span className="ci-mono ml-1.5 text-[11px] text-ink-3">{it.v}</span> : null}412                    </dd>413                  </div>414                ))}415                <div>416                  <dt className="text-ink-3">Eligibility</dt>417                  <dd>deaths ≥ 100 in the scope; a ratio is undefined (—) when the cancer has no trial / no publication.</dd>418                </div>419                <div>420                  <dt className="text-ink-3">Components</dt>421                  <dd className="ci-mono">{scope.formula_version}</dd>422                </div>423              </dl>424              <p className="mt-3 text-[12px] text-ink-3">425                <Link href="/methodology#research-gap" className="ci-link">426                  Full methodology427                </Link>{' '}428                · percentile-based indexes: <Link href="/rankings/trial_gap" className="ci-link">Trial Gap Index</Link>, <Link href="/rankings/research_gap" className="ci-link">Research Gap Index</Link>.429              </p>430            </Section>431            <Section id="caveats" kicker="Read with care" title="What this index is not" level={3}>432              <ul className="list-disc space-y-1.5 pl-5 text-[13px] text-ink-2">433                <li>Not a judgement of research quality, funding or difficulty — only registered activity counted against deaths. A positive ratio is a signal to look closer, not an accusation.</li>434                <li>Trial counts aggregate a cancer and its NCIt descendants; registrations phrased at a broader level (e.g. &ldquo;colorectal cancer&rdquo;) are attributed to the broader entity and can overstate the gap of narrower sites.</li>435                <li>Literature counts are query-based per entity (the query is stored with each count) and are not aggregated over descendants; a narrow query can inflate a Research Gap Ratio.</li>436                <li>Burden depends on the epidemiology source and its site definitions; the same cancer can have a different ratio under another source or year. Compare only within one table.</li>437                <li>United States only for now; a US death share is not a global death share.</li>438                <li>Shares are relative to the eligible set: adding or removing one cancer changes every other cancer&rsquo;s ratio slightly.</li>439              </ul>440            </Section>441          </div>442        </>443      )}444    </div>445  );446}447