Approximate likelihood and Bayesian methods

web.stanford.edu/class/stats364/

Jonathan Taylor

Spring 2020

Panigrahi et al. (2016)

File drawer problem

import numpy as np
from scipy.stats import norm as normal_dbn
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
# %matplotlib inline

ECDF = sm.distributions.ECDF
def pivot_plot(pivot):
    G = np.linspace(0, 1, 101)
    fig = plt.figure()
    ax = fig.gca()
    ax.plot(G, ECDF(pivot)(G), 'r', linewidth=4)
    ax.plot([0,1], [0,1], 'k--')
    ax.set_xlabel('ECDF(pivot)', fontsize=20)
    ax.set_ylabel('pivot', fontsize=20)
    return ax.figure
def simulate(truth, 
             precision,
             threshold=2, 
             level=0.9): 
    # (truth=\mu, precision=\lambda)
    C = normal_dbn.ppf((1 + level) / 2)
    prec = precision
    while True:
        D = np.random.standard_normal() + truth
        if D > threshold:
            L = D / (1 + prec) - C / np.sqrt(1 + prec)
            U = D / (1 + prec) + C / np.sqrt(1 + prec)
            break
    return L, U, (L < truth) * (U > truth)         

simulate(0, 0.2)
    
## (0.2217172670177403, 3.22479538460277, False)
coverage = []
for _ in range(1000):
    coverage.append(simulate(0, 0.5))
np.mean(coverage, 0)[2]
## 0.044
coverage = []
for _ in range(1000):
    coverage.append(simulate(0, 1))
np.mean(coverage, 0)[2]
## 0.569
coverage = []
for _ in range(1000):
    coverage.append(simulate(-1, 1))
np.mean(coverage, 0)[2]
## 0.0

Panigrahi et al. (2016)

How to correct?

Panigrahi et al. (2016)

Normalization

What about frequentist analysis?

# nonrandomized selection here
def simulate_corrected(truth, 
                       precision,
                       threshold=2, 
                       level=0.9,
                       ndraw=5000,
                       burnin=1000,
                       proposal_sd=0.5): 
    
    # (truth=\mu, precision=\lambda)
    
    prec = precision
    while True:
        D = np.random.standard_normal() + truth

        if D > threshold:

            def log_post(mu):
                if threshold - mu < 30:
                    value = np.log(normal_dbn.sf(threshold - mu))
                else:
                    value = - 0.5 * (threshold - mu)**2 - np.log(threshold - mu)
                return -(0.5 * (D - mu)**2 + 
                         value +
                         0.5 * prec * mu**2)

            # now draw B samples from Metropolis-Hastings
            chain = [D]
            den = log_post(chain[-1])
            for _ in range(ndraw + burnin):
                             
                proposal = (chain[-1] + 
                            np.random.standard_normal() * proposal_sd)
                
                try:
                    num = log_post(proposal)
                except:
                    num = - np.inf
                # acceptance probability
                 
                alpha = min(np.exp(num - den), 1)

                if np.random.binomial(1, alpha):
                    chain.append(1 * proposal)
                    den = num
                else:
                    chain.append(1 * chain[-1])
            
            chain = np.array(chain)[burnin:]
            
            U = np.percentile(chain, 100 * (1 - (1 - level) / 2))
            L = np.percentile(chain, 100 * (1 - level) / 2)
            break
    return L, U, (L < truth) * (U > truth)

simulate_corrected(-2, 0.2, ndraw=5000, proposal_sd=1)
## (-4.293642277299732, 1.8099147333531849, True)
coverage = []
for _ in range(50):
    coverage.append(simulate_corrected(-1, 0.2))
coverage = np.array(coverage)
coverage.mean(0)[2]
## 1.0
sns.distplot(coverage[:,1] - coverage[:,0])
## <matplotlib.axes._subplots.AxesSubplot object at 0x1a213a8dd0>
coverage = []
for _ in range(50):
    coverage.append(simulate_corrected(-1, 0.01))
coverage = np.array(coverage)
coverage.mean(0)[2]
## 0.94
sns.distplot(coverage[:,1] - coverage[:,0])
## <matplotlib.axes._subplots.AxesSubplot object at 0x1a213a8dd0>

Panigrahi et al. (2016)

Panigrahi et al. (2016)

File drawer

Panigrahi et al. (2016)

File drawer with randomization

# randomized selection here
def simulate_chernoff_freq(truth, 
                           threshold=2, 
                           level=0.9): 
    # truth=\mu
    C = normal_dbn.ppf((1 + level) / 2)
    while True:
        D = np.random.standard_normal() + truth
        omega = np.random.standard_normal()
        if D + omega > threshold:
            if D > threshold:
                L = D - C
                U = D + C
            else:
                L = 2 * D - threshold - C * np.sqrt(2)
                U = 2 * D - threshold + C * np.sqrt(2)
            break
    return L, U, (L < truth) * (U > truth)         

simulate_chernoff_freq(0)
## (-2.104403012045317, 2.547945602661378, True)
coverage = []
for _ in range(1000):
    coverage.append(simulate_chernoff_freq(-1, 0.01))
coverage = np.array(coverage)
coverage.mean(0)[2]
## 0.825

Panigrahi et al. (2016)

Chernoff MLE vs. truth

fig = plt.figure(figsize=(8, 8))
ax = plt.gca()
Zval = np.linspace(-10,10,501)
mle_val = np.linspace(-30,30,501)
chernoff = Zval * (Zval > 2) + (2 * Zval - 2) * (Zval <= 2)
Z_mle = mle_val + ((normal_dbn.pdf((2-mle_val) / np.sqrt(2)) / np.sqrt(2)) /
                    normal_dbn.sf((2-mle_val) / np.sqrt(2)))
ax.plot(Zval, chernoff, 'b', linewidth=3, label='Chernoff')
## [<matplotlib.lines.Line2D object at 0x1c2243b810>]
ax.plot(Z_mle, mle_val, 'r--', linewidth=3, label='MLE')
## [<matplotlib.lines.Line2D object at 0x1c231e2dd0>]
ax.set_xlabel('D', fontsize=15)
## Text(0.5, 0, 'D')
ax.set_ylabel(r'$\hat{\mu}(D)$', fontsize=20)
## Text(0, 0.5, '$\\hat{\\mu}(D)$')
ax.set_xlim([-1,5])
## (-1, 5)
ax.plot([2,2], [-5,10], 'k--')
## [<matplotlib.lines.Line2D object at 0x1c231ee790>]
ax.set_ylim([-5,6])
## (-5, 6)
ax.legend(fontsize=15)
## <matplotlib.legend.Legend object at 0x1c231ee550>

Panigrahi et al. (2016)

Bayesian inference using Chernoff approximation

# randomized selection here
def simulate_chernoff_bayesian(truth, 
                               precision,
                               threshold=2, 
                               level=0.9,
                               ndraw=5000,
                               burnin=1000,
                               proposal_sd=0.5): 
    
    prec = precision
    while True:
        D = np.random.standard_normal() + truth
        if D + np.random.standard_normal() > threshold:
            # now draw B samples from Metropolis-Hastings
            chain = [D]

            def log_post(mu):
                 return -(0.5 * (D - mu)**2 - 
                          0.25 * (mu - threshold)**2 * (D <= threshold) + 
                          0.5 * prec * mu**2)

            den = log_post(chain[-1])
            for _ in range(ndraw + burnin):
                             
                proposal = (chain[-1] + 
                            np.random.standard_normal() * proposal_sd)
                
                num = log_post(proposal)
                
                # acceptance probability
                 
                alpha = min(np.exp(num - den), 1)
                if np.random.binomial(1, alpha):
                    chain.append(1 * proposal)
                    den = num
                else:
                    chain.append(1 * chain[-1])
            
            chain = np.array(chain)[burnin:]
            
            U = np.percentile(chain, 100 * (1 - (1 - level) / 2))
            L = np.percentile(chain, 100 * (1 - level) / 2)
            break
    return L, U, (L < truth) * (U > truth)

simulate_chernoff_bayesian(-2, 0.2, ndraw=5000 ,proposal_sd=1)
## (-3.45454562713953, 0.4754887298734898, True)
coverage = []
for _ in range(1000):
    coverage.append(simulate_chernoff_bayesian(1, 0.0001))
coverage = np.array(coverage)
coverage.mean(0)[2]
## 0.801
sns.distplot(coverage[:,1] - coverage[:,0])
## <matplotlib.axes._subplots.AxesSubplot object at 0x1c231c9e90>

Panigrahi et al. (2016)

A smoother approximation

Panigrahi et al. (2016)

File drawer

Panigrahi et al. (2016)

Can this fix our long intervals problem?

def value(mu, threshold):
    T = threshold
    d_star = ((T + mu) + np.sqrt((T + mu)**2 - 4 * (T * mu - 1))) / 2
    val = (0.5 * (d_star - mu)**2 - np.log(d_star - T))
    return val
    
# nonrandomized selection here
def simulate_smooth_bayesian(truth, 
                             precision,
                             threshold=2, 
                             level=0.9,
                             ndraw=5000,
                             burnin=1000,
                             proposal_sd=0.5): 
    
    prec = precision
    while True:
        D = np.random.standard_normal() + truth
        if D > threshold:
            # now draw B samples from Metropolis-Hastings
            chain = [D]

            def log_post(mu):
                 return -(0.5 * (D - mu)**2 - 
                          value(mu, threshold) + 
                          0.5 * prec * mu**2)

            den = log_post(chain[-1])
            for _ in range(ndraw + burnin):
                             
                proposal = (chain[-1] + 
                            np.random.standard_normal() * proposal_sd)
                
                num = log_post(proposal)
                
                # acceptance probability
                 
                alpha = min(np.exp(num - den), 1)
                if np.random.binomial(1, alpha):
                    chain.append(1 * proposal)
                    den = num
                else:
                    chain.append(1 * chain[-1])
            
            chain = np.array(chain)[burnin:]
            
            U = np.percentile(chain, 100 * (1 - (1 - level) / 2))
            L = np.percentile(chain, 100 * (1 - level) / 2)
            break
    return L, U, (L < truth) * (U > truth)

simulate_smooth_bayesian(-2, 0.2, ndraw=5000 ,proposal_sd=1)
## (-3.9963500352340864, 1.6283124834639633, True)
coverage = []
for _ in range(100):
    coverage.append(simulate_smooth_bayesian(0, 0.0001))
coverage = np.array(coverage)
coverage.mean(0)[2]
## 0.67
sns.distplot(coverage[:,1] - coverage[:,0])
## <matplotlib.axes._subplots.AxesSubplot object at 0x1c231c9e90>

Panigrahi et al. (2016)

Back to MLE with randomization

def objective(soln, mu, threshold=2):
    d, omega = soln
    return 0.5 * ((d - mu)**2 + omega**2) - np.log(d + omega - threshold) 

def gradient(soln, mu, threshold=2):
    d, omega = soln
    return np.array([d - mu, omega]) - 1. / (d + omega - threshold)

def value_rand(mu, threshold, initial=None, tol=1.e-5):
    soln = np.array([threshold + 1, 0])
    cur_val = objective(soln, mu, threshold=threshold)
    alpha = 1
    count = 0
    stop = False
    while True:
        G = gradient(soln, mu, threshold=threshold)
        proposed = soln - alpha * G
        proposed_val = objective(proposed, mu, threshold=threshold)
        if ((proposed.sum() - threshold < 0) or
            (proposed_val > cur_val) or
            (np.isfinite(soln).sum() != 2)
            or (np.isfinite(G).sum() != 2)):
            alpha *= 0.5
        else:
            soln = proposed
            old_val, cur_val = cur_val, proposed_val
            if np.fabs(old_val - cur_val) <= tol * max(np.fabs(cur_val), 1):
                stop = True
        if stop:
            break
        count += 1
    return cur_val, soln
 
value_rand(0, 2)
## (2.177950604421338, array([1.37047989, 1.36157092]))
## 
## /Users/jonathantaylor/opt/anaconda3/bin/python3:3: RuntimeWarning: divide by zero encountered in log
# randomized selection here
def simulate_smooth_bayesian_rand(truth, 
                                  precision,
                                  threshold=2, 
                                  level=0.9,
                                  ndraw=5000,
                                  burnin=1000,
                                  proposal_sd=0.5): 
    
    prec = precision
    while True:
        D = np.random.standard_normal() + truth
        omega = np.random.standard_normal()
        if D + omega > threshold:
            # now draw B samples from Metropolis-Hastings
            chain = [D]

            def log_post(mu):
                 return -(0.5 * (D - mu)**2 - 
                          value_rand(mu, threshold)[0] + 
                          0.5 * prec * mu**2)

            den = log_post(chain[-1])
            for _ in range(ndraw + burnin):
                             
                proposal = (chain[-1] + 
                            np.random.standard_normal() * proposal_sd)
                
                num = log_post(proposal)
                
                # acceptance probability
                 
                alpha = min(np.exp(num - den), 1)
                if np.random.binomial(1, alpha):
                    chain.append(1 * proposal)
                    den = num
                else:
                    chain.append(1 * chain[-1])
            
            chain = np.array(chain)[burnin:]
            
            U = np.percentile(chain, 100 * (1 - (1 - level) / 2))
            L = np.percentile(chain, 100 * (1 - level) / 2)
            break
    return L, U, (L < truth) * (U > truth)

simulate_smooth_bayesian_rand(-2, 0.2, ndraw=5000, proposal_sd=1)
## (-3.6329185943845195, 0.29411072460259496, True)
## 
## /Users/jonathantaylor/opt/anaconda3/bin/python3:3: RuntimeWarning: invalid value encountered in log
coverage = []
for _ in range(50):
    coverage.append(simulate_smooth_bayesian_rand(-1, 0.01))
coverage = np.array(coverage)
coverage.mean(0)[2]
## 0.88
sns.distplot(coverage[:,1] - coverage[:,0])
## <matplotlib.axes._subplots.AxesSubplot object at 0x1c231c9e90>

Panigrahi & T (2019)

Frequentist inference by approximate MLE

Panigrahi & T (2019)

Frequentist inference by approximate MLE

Panigrahi & T (2019)

Frequentist inference by approximate MLE

def prox_barrier(d, value_barrier, grad_barrier, threshold=2, niter=100):
    
    soln = threshold + 1
    cur_val = (d - soln)**2 / 2 + value_barrier(soln, threshold=threshold)
    alpha = 1.
    for _ in range(niter):
        G = soln - d + grad_barrier(soln, threshold=threshold)
        while True:
            proposed = soln - alpha * G
            proposed_val = (d - proposed)**2 / 2 + value_barrier(proposed, threshold=threshold)
            if ((proposed_val > cur_val)
                or (proposed < threshold)):
                alpha *= 0.5
            else:
                cur_val = proposed_val
                soln = proposed
                alpha *= 4
                break
    return soln

def value_barrier(arg, threshold=2):
    return -np.log(arg - threshold)

def grad_barrier(arg, threshold=2):
    return -1 / (arg - threshold)

def hessian_barrier(arg, threshold=2):
    return 1 / (arg - threshold)**2 

def approx_mle(D,
               barrier,
               threshold=2):
    value, grad, hessian = barrier
    prox_soln = prox_barrier(D, value, grad)
    MLE = 2 * D - prox_soln
    info_MLE = 2 - 1 / (1 + hessian(prox_soln))
    return MLE, info_MLE

barrier = (value_barrier, grad_barrier, hessian_barrier)
approx_mle(2.4, barrier)
## (1.5801960968332591, 1.4019419322542626)
Zval = np.linspace(5, -1, 601)
ax.plot(Zval, [approx_mle(z, barrier)[0] for z in Zval], 'gray', linewidth=3, label=r'$B(\omega)=\log(\omega)$')
## [<matplotlib.lines.Line2D object at 0x1c2329ac90>]
## 
## /Users/jonathantaylor/opt/anaconda3/bin/python3:2: RuntimeWarning: divide by zero encountered in log
## /Users/jonathantaylor/opt/anaconda3/bin/python3:2: RuntimeWarning: invalid value encountered in log
ax.legend()
## <matplotlib.legend.Legend object at 0x1c23297fd0>
fig
## <Figure size 800x800 with 1 Axes>
def simulate_freq_pivot(truth, barrier, threshold=2):
    
    while True:
        
        D = np.random.standard_normal() + truth
        omega = np.random.standard_normal()
        if (D + omega) > threshold:
            MLE, info = approx_mle(D, barrier)
            Z = (MLE - truth) / np.sqrt(info)
            return Z

simulate_freq_pivot(0, barrier)
## 0.1297325145333067
Zsim = []
for _ in range(5000):
    Zsim.append(simulate_freq_pivot(-2, barrier))
axZ = sns.distplot(Zsim)
axZ.plot(np.linspace(-3,3,201), normal_dbn.pdf(np.linspace(-3,3,201)))
## [<matplotlib.lines.Line2D object at 0x1c23299d10>]
Zsim = []
for _ in range(5000):
    Zsim.append(simulate_freq_pivot(0, barrier))
axZ = sns.distplot(Zsim)
axZ.plot(np.linspace(-3,3,201), normal_dbn.pdf(np.linspace(-3,3,201)))
## [<matplotlib.lines.Line2D object at 0x1c2338ff10>]
Zsim = []
for _ in range(5000):
    Zsim.append(simulate_freq_pivot(4, barrier))
axZ = sns.distplot(Zsim)
axZ.plot(np.linspace(-3,3,201), normal_dbn.pdf(np.linspace(-3,3,201)))
## [<matplotlib.lines.Line2D object at 0x1c2329db50>]

Panigrahi & T (2019)

A slightly better (?) barrier

def value_barrier_alt(arg, threshold=2):
    return -np.log(arg - threshold) + np.log(arg - threshold + 1)

def grad_barrier_alt(arg, threshold=2):
    return -1 / (arg - threshold) + 1 / (arg - threshold + 1)

def hessian_barrier_alt(arg, threshold=2):
    return 1 / (arg - threshold)**2 - 1 / (arg - threshold + 1)**2

barrier_alt = (value_barrier_alt, grad_barrier_alt, hessian_barrier_alt)

Panigrahi & T (2019)

Zval = np.linspace(5, -1, 601)
ax.plot(Zval, [approx_mle(z, barrier_alt)[0] for z in Zval], 'k', linewidth=3, label=r'$B(\omega)=\log(\omega/(\omega+1))$')
## [<matplotlib.lines.Line2D object at 0x1c234c1310>]
ax.legend()
## <matplotlib.legend.Legend object at 0x1c2338f850>
fig
## <Figure size 800x800 with 1 Axes>

Panigrahi & T (2019)

Zsim = []
for _ in range(5000):
    Zsim.append(simulate_freq_pivot(-2, barrier_alt))
axZ = sns.distplot(Zsim)
axZ.plot(np.linspace(-3,3,201), normal_dbn.pdf(np.linspace(-3,3,201)))
## [<matplotlib.lines.Line2D object at 0x1c2320c350>]
Zsim = []
for _ in range(5000):
    Zsim.append(simulate_freq_pivot(0, barrier_alt))
axZ = sns.distplot(Zsim)
axZ.plot(np.linspace(-3,3,201), normal_dbn.pdf(np.linspace(-3,3,201)))
## [<matplotlib.lines.Line2D object at 0x1c2342fb50>]
Zsim = []
for _ in range(5000):
    Zsim.append(simulate_freq_pivot(2, barrier_alt))
axZ = sns.distplot(Zsim)
axZ.plot(np.linspace(-3,3,201), normal_dbn.pdf(np.linspace(-3,3,201)))
## [<matplotlib.lines.Line2D object at 0x1c231d3a90>]

Panigrahi & T (2019)

Density of MLE

\(\mu^*=-2\) \(\mu^*=0\) \(\mu^*=2\)

Panigrahi & T (2019)

Normal approximation

\(\mu^*=-2\) \(\mu^*=0\) \(\mu^*=2\)

Panigrahi & T (2019)

Fisher information

Panigrahi & T (2019)

Density of MLE pivot using observed information

Panigrahi & T (2019)

Density of MLE: effect of amount of follow-up data

\(\mu^*=-2\) \(\mu^*=0\) \(\mu^*=2\)

Follow-up sample of same size

Panigrahi & T (2019)

Density of MLE: effect of amount of follow-up data

\(\mu^*=-2\) \(\mu^*=0\) \(\mu^*=2\)

Keeping only 5% for inference

Panigrahi & T (2019)

Some questions

An answer: proximal representation of densities

Tian et al. (2016)

Density of MLE

Tian et al. (2016)

Tian et al. (2016)

Randomized LASSO

Tian et al. (2016)

Randomized LASSO

Tian et al. (2016)

Conditioning on the entire subgradient

Tian et al. (2016)

Conditioning on the entire subgradient

Panigrahi & T (2019)

Conditioning on the entire subgradient

Panigrahi & T (2019)

Selective MLE problem

Information for selective MLE

Panigrahi & T (2019)

Final piece for information

Exercise

  1. Suppose \(\omega \sim N(0, \Theta^{-1})\) what pieces above change?

  2. Suppose we work in the model \({\cal M} = \left\{Y|X \sim N(X\beta, \sigma^2 I): \beta \in \mathbb{R}^p, \sigma^2 >0 \right\}\) and we set \(\Theta^{-1} = \tau^2 (X^TX)\). Does anything simplify? Can you “make sense” of these quantities even when \(p>n\)?

  3. When will these estimates (and the information) revert to the (preselection) MLE? Does this match intuition?

Panigrahi and T. (2018)

Back to likelihood

Panigrahi and T. (2018)

Panigrahi and T. (2018)

from selectinf.randomized.lasso import lasso, selected_targets
from selectinf.randomized.posterior_inference import posterior_inference_lasso

n, p, s = 500, 100, 5

X = np.random.standard_normal((n, p))
beta = np.zeros(p)
beta[:s] = np.sqrt(2 * np.log(p) / n)
Y = X.dot(beta) + np.random.standard_normal(n)

Find selected variables

scale_ = np.std(Y)
# uses noise of variance n * scale_ / 4 by default
L = lasso.gaussian(X, Y, 3 * scale_ * np.sqrt(2 * np.log(p) * np.sqrt(n))) 
signs = L.fit()
E = (signs != 0)

Set up our targets

M = E.copy()
M[-3:] = 1
dispersion = np.linalg.norm(Y - X[:,M].dot(np.linalg.pinv(X[:,M]).dot(Y))) ** 2 / (n - M.sum())
(observed_target,
 cov_target,
 cov_target_score,
 alternatives) = selected_targets(L.loglike,
                                  L._W,
                                  M,
                                  dispersion=dispersion)
observed_target.shape, E.sum()
## ((12,), 9)
# interface a little clunky at the moment
# really just specifies quadratic of implied gaussian given subgradient

A_scaling = L.sampler.affine_con.linear_part
b_scaling = L.sampler.affine_con.offset
logdens_linear = L.sampler.logdens_transform[0]

posterior_inf = posterior_inference_lasso(observed_target,
                                          cov_target,
                                          cov_target_score,
                                          L.observed_opt_state,
                                          L.cond_mean,
                                          L.cond_cov,
                                          logdens_linear,
                                          A_scaling,
                                          b_scaling,
                                          observed_target)

samples = posterior_inf.posterior_sampler(nsample=400, nburnin=200, step=1.)
## /Users/jonathantaylor/git-repos/selectinf/selectinf/randomized/posterior_inference.py:90: RuntimeWarning: invalid value encountered in double_scalars
##   log_normalizer = -val - mean_marginal.T.dot(prec_marginal).dot(mean_marginal)/2
lower_cred = np.percentile(samples, 5, axis=0)
upper_cred = np.percentile(samples, 95, axis=0)
# clearly broken at the moment...
samples[:,0][:20]
## array([-4.11035730e+299,  1.93045796e+299, -9.06653034e+298,
##         4.25815917e+298, -1.99987413e+298,  9.39254826e+297,
##        -4.41127575e+297,  2.07178640e+297, -9.73028925e+296,
##         4.56989817e+296, -2.14628453e+296,  1.00801749e+296,
##        -4.73422439e+295,  2.22346148e+295, -1.04426418e+295,
##         4.90445952e+294, -2.30341359e+294,  1.08181425e+294,
##        -5.08081602e+293,  2.38624066e+293])
beta_target = np.linalg.pinv(X[:,M]).dot(X.dot(beta))
coverage_cred = (lower_cred < beta_target) * (upper_cred > beta_target)
coverage_cred
## array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
##         True,  True,  True])
try:
    S = L.selective_MLE(observed_target,
                        cov_target,
                        cov_target_score)[0]
    S
except ValueError as err:
    print("{0}".format(err))
## not finding a feasible point
def simulate_random_lasso():
    
    n, p = 500, 100

    X = np.random.standard_normal((n, p))
    beta = np.zeros(p)
    Y = np.random.standard_normal(n)
    
    scale_ = np.std(Y)
    
    # uses noise of variance n * scale_ / 4 by default
    L = lasso.gaussian(X, Y, 3 * scale_ * np.sqrt(2 * np.log(p) * np.sqrt(n))) 
    signs = L.fit()
    E = (signs != 0)
    M = E.copy()
    M[-3:] = 1
    
    dispersion = np.linalg.norm(Y - X[:,M].dot(np.linalg.pinv(X[:,M]).dot(Y))) ** 2 / (n - M.sum())
    
    (observed_target,
     cov_target,
     cov_target_score,
     alternatives) = selected_targets(L.loglike,
                                      L._W,
                                      M,
                                      dispersion=dispersion)
    
    S = L.selective_MLE(observed_target, 
                        cov_target,
                        cov_target_score,
                        level=0.9)[0]
    #print(S)
    cover = (S['lower'] < 0) * (S['upper'] > 0)
    return S['pvalue'], cover

p, c = simulate_random_lasso()
## /Users/jonathantaylor/opt/anaconda3/lib/python3.7/site-packages/pandas/core/computation/expressions.py:194: UserWarning: evaluating in Python space because the '*' operator is not supported by numexpr for the bool dtype, use '&' instead
##   op=op_str, alt_op=unsupported[op_str]
P, C = [], []
for _ in range(200):
    
    p, c = simulate_random_lasso()
    P.extend(p)
    C.extend(c)
    
('coverage', np.mean(C))
## ('coverage', 0.844179651695692)
pivot_plot(P);

Panigrahi & T (2019)

Screening rules

Panigrahi & T (2019)

Stepup procedures

Conditioning on winners and signs

Panigrahi & T (2019)

Randomized

Followup analysis

Panigrahi & T (2019)

Frequentist results

Panigrahi & T (2019)

Frequentist results