As cinco tabelas, as duas fontes de onde vêm, a cobertura real de cada coluna de enriquecimento e como consultar o resultado.
Cinco tabelas, exportadas em CSV delimitado por barra vertical UTF-8 e em Apache Parquet, com colunas idênticas:
position_index.Duas fontes, uma junção documentada. A URL, o horário de download e o SHA-256 de cada arquivo de origem ficam registrados no build_report.json fornecido com os dados.
categories_tags da fonte, listas de ingredientes brutas) sob a Open Database License (ODbL).NULL em todas as colunas derivadas do CosIng, nunca com um valor de preenchimento.is_common_allergen. A FDA dos EUA ainda não publicou sua lista de alergênicos de fragrância da MoCRA; por isso, não existe sinalização dos EUA neste conjunto de dados.rating_source e são NULL em todos os outros casos.Apresentada de duas formas, porque as duas diferem em uma ordem de grandeza e citar apenas uma induziria ao erro:
functions: 10,9% dos nomes distintos; 81,5% das ocorrências em rótulos.cas_number: 8,3% dos nomes distintos; 76,4% das ocorrências em rótulos.chemical_description: 8,5% dos nomes distintos, 3.965 valores distintos.Um corpus de rótulos contém muito mais strings distintas — variantes botânicas, grafias multilíngues, tokens de marketing — do que qualquer inventário regulatório lista, e é por isso que o primeiro número de cada par é baixo. As posições em um rótulo real são preenchidas, em sua esmagadora maioria, por ingredientes que o CosIng cobre, e é por isso que o segundo é alto. Nenhum dos dois números, isoladamente, descreve os dados.
Os tokens brutos dos rótulos são resolvidos em nomes INCI canônicos por um procedimento explícito e ordenado — decodificação de entidades, remoção de caracteres, colapso de espaços em branco, conversão para maiúsculas e, em seguida, correspondência exata, correspondência limpa, um pequeno mapa de erros de digitação com citação, remoção de percentuais, remoção de parênteses, um mapa de sinônimos, divisão de variantes com barra e divisão de concatenações somente quando ambas as metades correspondem ao inventário. Tudo o que falha em todas essas etapas é mantido literalmente e registrado como unresolved, nunca adivinhado. O método utilizado fica armazenado por linha em ingredient_name_map.method, de modo que cada decisão é auditável e reversível.
import pyarrow.parquet as pq
ingredients = pq.read_table('ingredients.parquet').to_pandas()
links = pq.read_table('product_ingredients.parquet').to_pandas()
# Coverage the way it actually matters: weighted by label occurrence.
merged = links.merge(ingredients, on='ingredient_id', how='left')
print(merged['functions'].notna().mean())
# EU Annex III fragrance allergens
allergens = ingredients[ingredients['is_common_allergen'] == 1]
import duckdb
duckdb.query("""
SELECT f.function, COUNT(*) AS label_slots
FROM 'product_ingredients.parquet' pi
JOIN 'ingredients.parquet' i USING (ingredient_id)
CROSS JOIN UNNEST(string_split(i.functions, ';')) AS f(function)
WHERE i.functions IS NOT NULL
GROUP BY 1 ORDER BY 2 DESC LIMIT 10;
""").show()
# Audit the canonicalisation itself
duckdb.query("""
SELECT method, COUNT(*) AS n
FROM 'ingredient_name_map.parquet'
GROUP BY 1 ORDER BY 2 DESC;
""").show()