It's a bit nuanced.
You could pull out the big guns and use a poisson regression
# example sample of counts (n = 50, 'true' mean = 5)
set.seed(25)
d <- rpois(50, 5)
model = glm(d~1, family = poisson)
exp(confint(model))
>>> 2.5 % 97.5 %
4.143126 5.348048
Or you could use the normal approximation since, as $\lambda$ gets big enough, the poisson starts to look a lot like a normal (though this only really happens when $\lambda$ is really really big). Be warned, if your sample is small and your $\lambda$ is close to zero, this may result in the CI covering values less than 0
mu = mean(d)
se = sd(d)/sqrt(length(d))
c(mu-1.96*se, mu+1.96*se)
>>>[1] 4.124606 5.315394
That the two estimates differ is perfectly acceptable since they make different assumptions. I would always prefer the former since the CI is computed using profile likelihood estimates.
Lastly, there is the procedure defined here though I think it adds little value over using glm
.
Here are some simulations to explore the coverage properties for both methods given your data generating process:
set.seed(0)
simGlmCi<-function(){
d <- rpois(50, 5)
model = glm(d~1)
ci = confint(model)
covered = (5<ci[2])&(ci[1]<5)
return(covered)
}
sims = purrr::rerun(1000,simGlmCi())
coverage = mean(unlist(sims))
coverage
>>>0.951
simNormalCi<-function(){
d <- rpois(50, 5)
mu = mean(d)
se = sd(d)/sqrt(length(d))
covered = (5<mu+1.96*se)&(mu-1.96*se)
return(covered)
}
sims = purrr::rerun(1000,simNormalCi())
coverage = mean(unlist(sims))
coverage
>>>0.964
You might be better off with the profile likelihood approach since it seems the normal approach is a bit permissive.
set.seed(25); d = rpois(50, 5); pm = c(-1,1); (t + 2 * pm* 1.96*sqrt(t+1))/50
returns (3.513047, 5.926953). $\endgroup$