tetrapoDNA
  • Visão geral
  • Espécies sem DNA
  • Por estado
  • Adote uma espécie
  • Fontes e método
Visão geral
cobertura_bruta = FileAttachment("data/cobertura.csv").csv({typed: true})
adocoes = FileAttachment("data/adocoes.csv").csv({typed: true})
status = FileAttachment("data/status.json").json()

REPO = "{{< meta website.repo-url >}}"

ROTULO_GRUPO = new Map([
  ["anfibios", "Anfíbios"], ["repteis", "Répteis"],
  ["aves", "Aves"], ["mamiferos", "Mamíferos"]
])
OPCOES_GRUPO = ["Todos", ...ROTULO_GRUPO.values()]
GRUPO_POR_ROTULO = new Map([...ROTULO_GRUPO].map(([k, v]) => [v, k]))

// Cores de situação (paleta de status: bom / atenção / crítico) e de grupo.
SITUACOES = ["com DNA", "a verificar", "sem DNA"]
COR_SITUACAO = ["#0ca30c", "#fab219", "#d03b3b"]
COR_GRUPO = ["#2a78d6", "#eb6834", "#1baf7a", "#eda100"]

AMEACADAS = new Set(["CR", "EN", "VU"])

cobertura = {
  const adot = new Map(adocoes.map(a => [a.especie, a]));
  return cobertura_bruta.map(d => {
    const a = adot.get(d.especie);
    const ufs = d.ufs ? String(d.ufs).split(";") : [];
    const cat = d.categoria_ameaca ?? "";
    return {
      ...d,
      grupo_rotulo: ROTULO_GRUPO.get(d.grupo),
      ufs_lista: ufs,
      endemica: d.endemica_br === true || d.endemica_br === "TRUE",
      ameacada: AMEACADAS.has(cat),
      adocao: a ? a.situacao : null,
      adotada_por: a ? a.instituicao : null,
      // prioridade: ameaçada > DD > endêmica > descrita há mais tempo
      prioridade: (AMEACADAS.has(cat) ? 0 : cat === "DD" ? 1 : 2) * 10 +
                  (d.endemica_br === true || d.endemica_br === "TRUE" ? 0 : 1)
    };
  });
}

filtra_grupo = (dados, rotulo) =>
  rotulo === "Todos" ? dados : dados.filter(d => d.grupo === GRUPO_POR_ROTULO.get(rotulo))

fmt = d3.format(",d")
fmtpct = d3.format(".0%")
fmtn = n => n == null ? "" : fmt(n).replace(/,/g, ".")

conta = dados => {
  const c = d3.rollup(dados, v => v.length, d => d.situacao);
  return {
    total: dados.length,
    com: c.get("com DNA") ?? 0,
    verificar: c.get("a verificar") ?? 0,
    sem: c.get("sem DNA") ?? 0
  };
}

link_adote = especie =>
  `${REPO}/issues/new?template=adote.yml&title=${encodeURIComponent("Adoção: " + especie)}&especie=${encodeURIComponent(especie)}`

selo_situacao = s => {
  const cls = s === "com DNA" ? "com" : s === "sem DNA" ? "sem" : "verificar";
  const ico = s === "com DNA" ? "✓" : s === "sem DNA" ? "✕" : "?";
  return htl.html`<span class="situacao ${cls}">${ico} ${s}</span>`;
}
sel = filtra_grupo(cobertura, grupo)
n = conta(sel)
txt_total = fmtn(n.total)
txt_com = `${fmtn(n.com)} (${fmtpct(n.com / n.total)})`
txt_verificar = `${fmtn(n.verificar)} (${fmtpct(n.verificar / n.total)})`
txt_sem = `${fmtn(n.sem)} (${fmtpct(n.sem / n.total)})`

Espécies na lista

Com DNA público

A verificar

Sem nenhum DNA encontrado

Situação por grupo
{
  const linhas = [];
  for (const [g, rot] of ROTULO_GRUPO) {
    const c = conta(cobertura.filter(d => d.grupo === g));
    for (const [s, v] of [["com DNA", c.com], ["a verificar", c.verificar], ["sem DNA", c.sem]])
      linhas.push({grupo: rot, situacao: s, n: v, p: v / c.total});
  }
  return Plot.plot({
    marginLeft: 80, height: 210, width: 640, style: {fontSize: "13px"},
    x: {percent: true, label: "% das espécies →", grid: true},
    y: {label: null, domain: [...ROTULO_GRUPO.values()]},
    color: {domain: SITUACOES, range: COR_SITUACAO, legend: true},
    marks: [
      Plot.barX(linhas, Plot.stackX({x: "p", y: "grupo", fill: "situacao", order: SITUACOES,
        inset: 1, tip: true, title: d => `${d.grupo} · ${d.situacao}: ${fmtn(d.n)} (${fmtpct(d.p)})`})),
      Plot.text(linhas.filter(d => d.p > 0.07), Plot.stackX({x: "p", y: "grupo", order: SITUACOES,
        z: "situacao", text: d => fmtpct(d.p),
        fill: d => d.situacao === "a verificar" ? "#0b0b0b" : "white"}))
    ]
  });
}
Registros no BOLD por espécie (faixas do iBOL Gap List)
{
  const faixas = ["0", "1-2", "3-9", "10+"];
  const c = d3.rollup(sel, v => v.length, d => String(d.faixa_bold));
  const dados = faixas.map(f => ({faixa: f, n: c.get(f) ?? 0}));
  return Plot.plot({
    height: 240, width: 520, marginLeft: 50, style: {fontSize: "13px"},
    x: {domain: faixas, label: "registros no BOLD"},
    y: {label: "espécies", grid: true},
    marks: [
      Plot.barY(dados, {x: "faixa", y: "n", fill: "#2a78d6", rx: 4, inset: 2, tip: true}),
      Plot.text(dados, {x: "faixa", y: "n", text: d => fmtn(d.n), dy: -8}),
      Plot.ruleY([0])
    ]
  });
}
Famílias com mais espécies sem DNA
{
  const fam = d3.rollups(sel.filter(d => d.situacao !== "com DNA"),
    v => ({sem: v.filter(d => d.situacao === "sem DNA").length,
           verificar: v.filter(d => d.situacao === "a verificar").length, total: v.length}),
    d => d.familia)
    .map(([familia, v]) => ({familia, ...v}))
    .sort((a, b) => d3.descending(a.total, b.total)).slice(0, 15);
  const linhas = fam.flatMap(f => [
    {familia: f.familia, situacao: "sem DNA", n: f.sem},
    {familia: f.familia, situacao: "a verificar", n: f.verificar}]);
  return Plot.plot({
    marginLeft: 130, height: Math.max(200, fam.length * 22), width: 560, style: {fontSize: "13px"},
    x: {label: "espécies →", grid: true},
    y: {label: null, domain: fam.map(d => d.familia)},
    color: {domain: ["a verificar", "sem DNA"], range: ["#fab219", "#d03b3b"], legend: true},
    marks: [
      Plot.barX(linhas, Plot.stackX({x: "n", y: "familia", fill: "situacao",
        order: ["sem DNA", "a verificar"], inset: 1, tip: true})),
      Plot.ruleX([0])
    ]
  });
}
Descritas há mais tempo e ainda sem DNA
{
  const velhas = sel.filter(d => d.situacao === "sem DNA" && d.ano_descricao)
    .sort((a, b) => d3.ascending(a.ano_descricao, b.ano_descricao)).slice(0, 25);
  return Inputs.table(velhas, {
    columns: ["especie", "grupo_rotulo", "ano_descricao", "categoria_ameaca"],
    header: {especie: "Espécie", grupo_rotulo: "Grupo", familia: "Família",
             ano_descricao: "Descrita em", categoria_ameaca: "Ameaça"},
    format: {especie: d => htl.html`<a href="${link_adote(d)}" target="_blank" title="Quero sequenciar"><em>${d}</em></a>`,
             ano_descricao: d => String(d)},
    rows: 25, layout: "auto", select: false
  });
}
Novidades: espécies que ganharam DNA
{
  const ev = await FileAttachment("data/eventos.csv").csv({typed: true});
  if (!ev.length) return md`Nenhuma ainda. A cada atualização semanal, toda espécie que sair da lista "sem DNA" aparece aqui.`;
  return Inputs.table(ev.reverse(), {
    columns: ["data", "especie", "grupo", "n_bold", "n_genbank"],
    header: {data: "Data", especie: "Espécie", grupo: "Grupo", n_bold: "BOLD", n_genbank: "GenBank"},
    rows: 10, select: false
  });
}
viewof grupo = Inputs.radio(OPCOES_GRUPO, {label: "Grupo", value: "Todos"})
md`**Pergunta:** quantas espécies de tetrápodes do Brasil ainda não têm nenhuma sequência de DNA pública?

Uma espécie conta como **com DNA** quando há ao menos um registro público no [BOLD](https://boldsystems.org) ou no [GenBank](https://www.ncbi.nlm.nih.gov/nuccore) sob o nome da lista oficial do grupo (ou sob combinação antiga do mesmo epíteto).

**A verificar** = aparece sem DNA, mas há motivo para desconfiar do nome (sinônimo, nome ausente do CTFB, gênero muito sequenciado). Veja [Fontes e método](sobre.html).

<span class="nota-pequena">Atualizado em ${status.atualizado_em}. Atualização automática semanal.</span>`

Dados: BOLD Systems, NCBI GenBank, CTFB/JBRJ e listas da SBH, CBRO e SBMz (ver Fontes).

 
  • Criar uma issue
  • Ver o código fonte

Código MIT · Dados CC-BY 4.0