sequencial  ·  reposição mínima de probabilidade  ·  Chromy (1979)

Cada unidade a menos de
um da sua expectativa.

O método de Chromy seleciona as unidades uma a uma ao longo de uma lista ordenada, cada qual com probabilidade proporcional ao seu tamanho, de modo que uma unidade esperada 0,3 vezes é selecionada 0 ou 1 vez, uma esperada 2,4 vezes é selecionada 2 ou 3 — nunca mais longe — e o tamanho da amostra é sempre exato. É assim que muitos grandes levantamentos sorteiam as suas amostras, e é assim que funciona o PROC SURVEYSELECT METHOD=PPS_SEQ. Até agora o método vivia dentro de software proprietário e de um módulo de Stata. chromy é o método em código aberto: um motor em Rust, vinculado a R e a Python.

um desenho completo, em R
s <- chromy_sample(frame, n = 40, size = "pupils",
                   order_by = c("region", "chromy_size"), seed = 123)

sum(s$sample$hits)                      40   # sempre: exato, não esperado
sum(s$expected_hits)                    40
estimate_total(s, "outcome")            total 37623, EP 5010 (CV 13,3%)

A mesma semente dá a mesma amostra, até o último sorteio — a aleatoriedade vem do gerador do próprio R, nunca de dentro do motor.

o método

Uma caminhada pela lista, um sorteio condicional por unidade.

Os hits esperados se acumulam ao longo da lista ordenada. Cada vez que a soma corrente cruza um inteiro, uma seleção é liberada; um único uniforme por unidade decide, condicionalmente à caminhada até ali, se a seleção pendente cai aqui ou segue adiante. Os condicionais são construídos para que os hits de cada unidade saiam exatamente com a probabilidade que a sua expectativa exige — uma implementação sequencial do desenho, não uma aproximação dele.

Seis unidades cujos hits esperados somam três; o caminho acumulado cruza três níveis inteiros, e cada cruzamento libera uma seleção.

tamanho exato

Os hits somam n em toda realização. Não em média: sempre.

reposição mínima

Os hits são o piso ou o teto da expectativa — uma unidade pode ser selecionada mais de uma vez, mas nunca se afasta do que o desenho lhe prometeu.

estratificação implícita

Unidades vizinhas na lista são selecionadas afastadas, e a amostra se espalha sobre o que quer que as chaves de ordenação codifiquem.

as ferramentas

O aparato com que o método é usado na prática.

Um procedimento de seleção sozinho é metade de um desenho. Em volta dele, as peças que os levantamentos que dependem do método de Chromy de fato usam — cada uma é uma função do motor, cada uma testada contra a mesma referência.

ordenação serpentina

A ordenação hierárquica que inverte a direção em cada fronteira de grupo, de modo que a lista serpenteia pelo cadastro e as unidades vizinhas são sempre parecidas. Chaves categóricas ordenam por bytes, nunca pelo locale: o mesmo cadastro ordena igual em qualquer máquina.

medida de tamanho maximal

Para levantamentos que precisam estimar vários totais com uma só amostra: o tamanho de cada unidade é a maior das suas participações escalonadas entre as variáveis auxiliares (Folsom, Potter e Williams, 1987), e toda variável recebe ao menos a cobertura que pretendia.

alocação de CVs iguais

Os fatores que decidem quanta amostra cada variável comanda, buscados até que o total de toda variável seja antecipado com o mesmo coeficiente de variação — nenhuma estimativa sacrificada em silêncio a outra.

estimação por multiplicidade

O estimador que corresponde ao desenho — valores divididos pelos hits esperados efetivos, ponderados pelos hits recebidos — com a variância de diferenças sucessivas lida na ordem da lista, onde a serpentina deixou vizinhos genuinamente parecidos.

um motor, três linguagens

O motor recebe a aleatoriedade como argumento.

chromy-core não tem dependências nem gerador: os uniformes chegam como argumentos, e uma seleção é uma função pura das suas entradas. O R os sorteia com o gerador do próprio R — e é por isso que set.seed() reproduz o protótipo original sorteio a sorteio — e o Python os sorteia de uma semente documentada. Três linguagens, uma aritmética, uma referência.

Um cadastro com tamanhos e os uniformes do chamador entram no chromy-core, usado de Rust, do R como o pacote chromy e do Python como pychromy; testes-ouro fixam os três a uma referência.

R · chromy

Data frames entram, tibbles saem; o desenho inteiro numa chamada; quatro vinhetas, da caminhada à validação.

site do pacote ↗

Python · pychromy

Listas de colunas entram, dicts de listas paralelas saem — nada para instalar além da wheel; pandas opcional por construção.

site do pacote ↗

Rust · chromy-core

O próprio motor, para um sistema de produção que sorteia as próprias amostras; slices entram, structs saem.

docs.rs ↗

fidelidade

O ponto flutuante faz parte do desenho.

Hits esperados que deveriam somar um inteiro raramente somam em doubles, e um resto que cai em 1 − 10−16 é ou não é um cruzamento — a resposta muda quem pode ser selecionado. O motor mede o desvio com soma compensada, recusa-o além de 10−8 em vez de reescalar as probabilidades do chamador, atribui o que resta a uma única unidade e relata cada ajuste que fez. Os hits esperados efetivos que devolve são os denominadores pelos quais o estimador divide.

O ecossistema inteiro está fixado a uma única implementação de referência por fixtures-ouro: saídas inteiras exatas, ponto flutuante na precisão da máquina — e o pacote R vai além, reproduzindo a referência semente a semente.

a contabilidade, relatada com cada amostra
s$accounting
$eps_sum              -8.43e-15   # o desvio medido
$corrected_unit       512         # quem o absorveu
$adjusted_crossings   0
$max_adjustment       8.43e-15

# e uma recusa é uma recusa:
Error: the expected hits sum 3.2e-2 away from an
integer, beyond the 1e-8 tolerance; they do not
define a sample size

instalar

Três portas para o mesmo motor.

R
pak::pak(
  "castlaboratory/chromy")
Python
pip install pychromy
Cargo.toml
[dependencies]
chromy-core = "0.1"

O pacote R compila do código-fonte com uma toolchain Rust (≥ 1.77) até os binários do CRAN estarem no ar; o crate em si não tem dependência alguma.