Gerando variáveis ​​aleatórias a partir de uma mistura de distribuições normais

20

Como posso amostrar a partir de uma distribuição de mistura e, em particular, uma mistura de distribuições normais R? Por exemplo, se eu quisesse provar de:

0,3×N(0 0,1)+0,5×N(10,1)+0,2×N(3,.1)

como eu pude fazer isso?

- Reinstate Monica
fonte
3
Eu realmente não gosto dessa maneira de denotar uma mistura. Sei que é convencionalmente feito assim, mas acho enganoso. A notação sugere que, para provar, você precisa provar todos os três valores normais e pesar os resultados pelos coeficientes que obviamente não seriam corretos. Alguém conhece uma notação melhor?
StijnDeVuyst
Eu nunca tive essa impressão. Penso nas distribuições (neste caso, as três distribuições normais) como funções e, em seguida, o resultado é outra função.
roundsquare
@StijnDeVuyst, você pode querer visitar esta pergunta com base no seu comentário: stats.stackexchange.com/questions/431171/…
ankii
@ankii: obrigado por apontar isso!
StijnDeVuyst 14/10

Respostas:

32

É uma boa prática evitar forloops Rpor razões de desempenho. Uma solução alternativa que explora o fato rnormé vetorizada:

N <- 100000

components <- sample(1:3,prob=c(0.3,0.5,0.2),size=N,replace=TRUE)
mus <- c(0,10,3)
sds <- sqrt(c(1,1,0.1))

samples <- rnorm(n=N,mean=mus[components],sd=sds[components])
M. Berk
fonte
3
Como alternativa, você pode usar as propriedades da distribuição normal para substituir a última linha por samples <- rnorm(N)*sds[components]+mus[components]. Acho que é mais fácil de ler :)
Elvis
Muito elegante (cc @Elvis)!
Itamar
18

Em geral, uma das maneiras mais fáceis de coletar amostras de uma distribuição de mistura é a seguinte:

Etapas do algoritmo

1) Gere uma variável aleatória vocêUniforme(0 0,1)

você[Eu=1kpk,Eu=1k+1pk+1)pkkthkth

3) Repita as etapas 1) e 2) até obter a quantidade desejada de amostras da distribuição da mistura

Agora, usando o algoritmo geral fornecido acima, você pode fazer uma amostra do seu exemplo de mistura de normais usando o seguinte Rcódigo:

#The number of samples from the mixture distribution
N = 100000                 

#Sample N random uniforms U
U =runif(N)

#Variable to store the samples from the mixture distribution                                             
rand.samples = rep(NA,N)

#Sampling from the mixture
for(i in 1:N){
    if(U[i]<.3){
        rand.samples[i] = rnorm(1,0,1)
    }else if(U[i]<.8){
        rand.samples[i] = rnorm(1,10,1)
    }else{
        rand.samples[i] = rnorm(1,3,.1)
    }
}

#Density plot of the random samples
plot(density(rand.samples),main="Density Estimate of the Mixture Model")

#Plotting the true density as a sanity check
x = seq(-20,20,.1)
truth = .3*dnorm(x,0,1) + .5*dnorm(x,10,1) + .2*dnorm(x,3,.1)
plot(density(rand.samples),main="Density Estimate of the Mixture Model",ylim=c(0,.2),lwd=2)
lines(x,truth,col="red",lwd=2)

legend("topleft",c("True Density","Estimated Density"),col=c("red","black"),lwd=2)

O que gera:

insira a descrição da imagem aqui

e como verificação de sanidade:

insira a descrição da imagem aqui


fonte
Oi! Muito obrigado! Essa resposta me ajudou muito. Estou usando isso em um projeto de pesquisa. Desejo citar uma referência para o exposto acima. Você pode sugerir uma citação de artigo de pesquisa.
Abhishek Bhatia
7

kR

set.seed(8)               # this makes the example reproducible
N     = 1000              # this is how many data you want
probs = c(.3,.8)          # these are *cumulative* probabilities; since they 
                          #   necessarily sum to 1, the last would be redundant
dists = runif(N)          # here I'm generating random variates from a uniform
                          #   to select the relevant distribution

# this is where the actual data are generated, it's just some if->then
#   statements, followed by the normal distributions you were interested in
data = vector(length=N)
for(i in 1:N){
  if(dists[i]<probs[1]){
    data[i] = rnorm(1, mean=0, sd=1)
  } else if(dists[i]<probs[2]){
    data[i] = rnorm(1, mean=10, sd=1)
  } else {
    data[i] = rnorm(1, mean=3, sd=.1)
  }
}

# here are a couple of ways of looking at the results
summary(data)
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
# -3.2820  0.8443  3.1910  5.5350 10.0700 13.1600 

plot(density(data))

insira a descrição da imagem aqui

- Reinstate Monica
fonte
Resposta agradável, você me venceu em postar: P
1
Obrigado pela dica, @BabakP. Não tenho certeza do que era. Era algo na ifelse()declaração, mas vou ter que descobrir isso mais tarde. Substituí esse código com um loop.
gung - Restabelece Monica
6
RfindInterval()cumsum()μmuσ2spmix <- function(n,mu,s,p) { ii <- findInterval(runif(n),cumsum(p))+1; x <- rnorm(n,mean=mu[ii],sd=sqrt(s[ii])); return(x); }
1
@ Macro, código muito verdadeiro e muito bom! Eu nunca vi o findInterval()comando antes, no entanto, gosto de escrever código aqui da maneira mais simplista possível, porque quero que seja uma ferramenta para entender e não para eficiência.
1
Eu disse que estas eram boas respostas. Meu objetivo não era criticá-lo, mas oferecer uma abordagem que generalizasse facilmente para mais de três dimensões, alterando apenas um único argumento, não qualquer código. Não está claro para mim por que o que você escreveu é mais transparente do que o que escrevi, mas certamente não quero discutir sobre isso. Felicidades.
Macro
0

Já recebi respostas perfeitas, então, para quem quer conseguir isso em Python, aqui está a minha solução:

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

mu = [0, 10, 3]
sigma = [1, 1, 1]
p_i = [0.3, 0.5, 0.2]
n = 10000

x = []
for i in range(n):
    z_i = np.argmax(np.random.multinomial(1, p_i))
    x_i = np.random.normal(mu[z_i], sigma[z_i])
    x.append(x_i)

def univariate_normal(x, mean, variance):
    """pdf of the univariate normal distribution."""
    return ((1. / np.sqrt(2 * np.pi * variance)) * 
            np.exp(-(x - mean)**2 / (2 * variance)))

a = np.arange(-7, 18, 0.01)
y = p_i[0] * univariate_normal(a, mean=mu[0], variance=sigma[0]**2) + p_i[1] * univariate_normal(a, mean=mu[1], variance=sigma[0]**2)+ p_i[2] * univariate_normal(a, mean=mu[2], variance=sigma[0]**2)

fig, ax = plt.subplots(figsize=(8, 4))

ax.hist(x, bins=100, density=True)
ax.plot(a, y)

insira a descrição da imagem aqui

UM RATO
fonte