Our memory experiments revealed that the threshold of Lac proteins required to be readily sensitive to lactose as an inducer is on the order of 1 molecule. Accordingly, we predict that the fraction of short and long lags will change if we perturb the expression of the lac operon in its repressed state.

Perturbing LacI activity

First, we perturb the repressed lac operon expression by manipulating LacI activity: using minute amounts of artificial inducer (IPTG), we can titrate a given fraction of LacI, conversly, LacI can be mildly overexpressed from a low copy number plasmid.

In a series of preliminary experiments, we check how to titrate LacI with IPTG: xxx In the following, we add IPTG only before the switch.

(myplots[['cmp_iptg_inhib']] <- 
mycells_switching %>% ungroup() %>% 
  filter(!date %in% discarded_dates) %>%
  filter(!discard_arrested) %>% 
  filter(switch_idx==1) %>%
  filter(str_detect(condition, '^switch_[0-9]+h$') | 
           str_detect(condition, 'withIPTG5uM') |  str_detect(condition, 'lacIPTG5uM') | 
           str_detect(condition, 'preIPTG5uM') ) %>% 
  mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'lactose', .x)),
         laci=factor(ifelse(condition=='lactose', 'control', 'lower')) %>% fct_relevel('lower', 'control') ) %>%
  mutate(lag=lag_200,
         lag=ifelse(!is.infinite(lag), lag, max(lag[is.finite(lag)])+60),
         lag=ifelse(is.na(lag), -200, lag)) %>% 
  ggplot(aes(x=condition, y=lag/60)) +
  geom_violin(aes(group=date, col=laci, fill=laci), adjust=0.7, 
              alpha=.4, position='identity') +
  geom_hline(yintercept=50, lty='dashed') +
  # ylim(0, 240) +
  scale_x_discrete(breaks=c('lactose', 'switch_withIPTG5uM', 'switch_lacIPTG5uM', 'switch_preIPTG5uM'), 
                   labels=c('glucose\n> lactose', 'glucose+IPTG\n> lactose+IPTG', 
                            'glucose\n> lactose+IPTG', 'glucose+IPTG\n> lactose') )+ 
  expand_limits(y=0) +
  labs(y=lac_lags_label, col='LacI activity', fill='LacI activity') +
  theme_cowplot_legend_inset() +
  theme(#axis.text.x=element_text(angle=45, hjust=1.25, vjust=1.1), 
        axis.text.x=element_text(size=rel(1), colour='black'),
        axis.title.x=element_blank(),
        legend.position='top') +
  NULL
) +
  labs(subtitle='with [IPTG] = 5µM')

According to our prediction, no short lags are observed with reduced basal expression and the fraction of long lags increases dramatically when basal expression is increased with 5µM IPTG. Supplementing IPTG can also compensate the effect of LacI overexpression.

(myplots[['basal_perturb_pre_violin']] <- function(.yannot=-10, .annot_size=3)
   mycells_switching %>% ungroup() %>% 
   filter(!date %in% discarded_dates) %>%
   filter(!discard_arrested) %>% 
   filter(switch_idx==1) %>%
   filter(str_detect(condition, '^switch_[0-9]+h$') |
            str_detect(condition, 'preIPTG') | str_detect(condition, 'lacIoe$')) %>% 
   mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'control', .x)),
          laci=fct_recode(condition, 'lower'='switch_preIPTG5uM',
                          'lower'='switch_lacIoe_preIPTG10uM',
                          'higher'='switch_lacIoe') %>% 
            fct_relevel('lower', 'control'),
          condition=fct_relevel(condition, 'switch_lacIoe', "control",
                                'switch_lacIoe_preIPTG10uM', 'switch_preIPTG5uM') %>% 
            fct_recode('+ 5µM IPTG'='switch_preIPTG5uM',
                       '+ LacI plasmid'='switch_lacIoe', 
                       '+ LacI plasmid\n+ 10µM IPTG'='switch_lacIoe_preIPTG10uM') ) %>% 
   mutate(lag=lag_200,
          lag=ifelse(lag>240*60, Inf, lag)
   ) %>% 
   (function(.df)
     ggplot(.df, aes(x=condition, y=lag/60)) +
      geom_hline(yintercept=50, lty='dashed') +
      geom_violin(aes(group=date, fill=laci, col=laci), adjust=.7, width=1.1, alpha=.4, position='identity') +
      geom_text(aes(y=.yannot, label=annot), vjust=0.5, size=.annot_size, lineheight=1,
                data=.df %>% group_by(condition, laci) %>% 
                  summarise(n=n(), type='short', prop=sum(lag/60<50, na.rm=TRUE)/sum(!is.na(lag)),
                            annot=sprintf("%.0f%%\nn=%d", prop*100, n)) ) +
      coord_cartesian(ylim=c(-35, 240), expand=FALSE) +
      labs(y=lac_lags_label, col='LacI activity', fill='LacI activity') +
      guides(fill=guide_legend(override.aes=list(alpha=1), reverse=TRUE), col=guide_legend(reverse=TRUE)) +
      theme_cowplot_legend_inset(0.8) +
      theme(axis.title.x=element_blank(),
            axis.text.x=element_text(size=rel(1), colour='black',
                                     angle=45, hjust=1, vjust=0.5, margin=margin(t=-28, b=34)),
            legend.margin=margin(),
            # legend.title=element_text(size=rel(0.7), face='bold'),
            # legend.text=element_text(size=rel(0.7)),
            legend.position='top') +
      NULL
   ) )()

(myplots[['basal_perturb_pre_frac_short']] <- 
   mycells_switching %>% ungroup() %>% 
   filter(!date %in% discarded_dates) %>%
   filter(!discard_arrested) %>% 
   filter(switch_idx==1) %>%
   filter(str_detect(condition, '^switch_[0-9]+h$') |
            str_detect(condition, 'preIPTG') | str_detect(condition, 'lacIoe$')) %>% 
   mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'control', .x)),
          laci=fct_recode(condition, 'lower'='switch_preIPTG5uM',
                          'lower'='switch_lacIoe_preIPTG10uM',
                          'higher'='switch_lacIoe') %>% 
            fct_relevel('lower', 'control'),
          condition=fct_relevel(condition, 'switch_lacIoe', "control",
                                'switch_lacIoe_preIPTG10uM', 'switch_preIPTG5uM') %>% 
            fct_recode('with 5µM IPTG'='switch_preIPTG5uM',
                       'with LacI plasmid'='switch_lacIoe', 
                       'with LacI plasmid\n+ 10µM IPTG'='switch_lacIoe_preIPTG10uM') ) %>% 
    mutate(lag=lag_200,
           lag=ifelse(lag>240*60, Inf, lag)
    ) %>% 
    group_by(condition, laci) %>% 
    summarise(n=n(), type='short', prop=sum(lag/60<50, na.rm=TRUE)/sum(!is.na(lag)), 
              prop_se=sqrt(prop*(1-prop)/n)) %>% 
    ggplot(aes(x=condition, y=prop)) +
    geom_bar(aes(y=1), fill='gray95', stat='identity') +
    geom_bar(aes(fill=laci), stat='identity') +
    # geom_rect(aes(xmin=as.numeric(condition)-.45, xmax=as.numeric(condition)+.45, 
    #               ymin=prop-prop_se, ymax=prop+prop_se), data=.df) +
    geom_errorbar(aes(ymin=prop-prop_se, ymax=prop+prop_se), width=.25) +
    geom_text(aes(y=1, label=sprintf('n=%d', n)), vjust=1.3) +
    labs(y='prop. of short lags') +
    # expression(paste("type of ", italic("lac"), " induction lag"))
    theme_cowplot_legend_inset(0.8) +
    theme(axis.title.x=element_blank(),
          axis.text.x=element_text(size=rel(1), colour='black',
                                   angle=45, hjust=0.8, vjust=0.9),
          legend.margin=margin(),
          legend.position='top') +
    NULL
)

mycells_switching %>% ungroup() %>% 
  filter(!date %in% discarded_dates) %>%
  filter(!discard_arrested) %>% 
  filter(switch_idx==1) %>%
  filter(str_detect(condition, '^switch_[0-9]+h$') |
           str_detect(condition, 'preIPTG') | str_detect(condition, 'lacIoe$')) %>% 
  mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'control', .x)),
         laci=fct_recode(condition, 'lower'='switch_preIPTG5uM', 
                         'lower'='switch_lacIoe_preIPTG10uM',
                         'higher'='switch_lacIoe') %>% 
           fct_relevel('lower', 'control'),
         condition=fct_relevel(condition, 'switch_lacIoe', "control", 
                               'switch_lacIoe_preIPTG10uM', 
                               'switch_preIPTG5uM') %>% 
           fct_recode('with 5µM IPTG'='switch_preIPTG5uM',
                      'with LacI plasmid'='switch_lacIoe', 
                      'with LacI plasmid\n+ 10µM IPTG'='switch_lacIoe_preIPTG10uM') ) %>% 
  mutate(
    lag_type=lag_200,
    lag_type=as.character(lag_type), lag_type=ifelse(is.na(lag_type), 'NA', lag_type),
    lag_type=ifelse(is.finite(as.numeric(lag_type)) & as.numeric(lag_type)<50*60, 'short lags', lag_type),
    lag_type=ifelse(is.finite(as.numeric(lag_type)) & as.numeric(lag_type)>=50*60, 'long lags', lag_type) ) %>%
  group_by(condition, lag_type) %>% 
  summarise(n=n(), m=mean(lag_200/60), sd=sd(lag_200/60)) %>% 
  group_by(condition) %>% mutate(p=n/sum(n), p_se=sqrt(p*(1-p)/n)) %>% 
  knitr::kable(digits=3) %>% 
  kableExtra::kable_styling() %>% 
  identity()
condition lag_type n m sd p p_se
with LacI plasmid Inf 3 Inf NaN 0.009 0.054
with LacI plasmid long lags 281 127.463 67.399 0.841 0.022
with LacI plasmid NA 31 NA NA 0.093 0.052
with LacI plasmid short lags 19 39.000 7.810 0.057 0.053
control Inf 52 Inf NaN 0.032 0.024
control long lags 1061 89.601 33.097 0.650 0.015
control NA 106 NA NA 0.065 0.024
control short lags 414 34.261 7.056 0.254 0.021
with LacI plasmid + 10µM IPTG long lags 78 77.154 21.339 0.448 0.056
with LacI plasmid + 10µM IPTG NA 14 NA NA 0.080 0.073
with LacI plasmid + 10µM IPTG short lags 82 36.951 5.923 0.471 0.055
with 5µM IPTG Inf 2 Inf NaN 0.006 0.053
with 5µM IPTG long lags 112 67.554 13.944 0.322 0.044
with 5µM IPTG NA 20 NA NA 0.057 0.052
with 5µM IPTG short lags 214 33.393 7.309 0.615 0.033

A similar picture is obtained when IPTG is added before and after the switch:

(myplots[['basal_perturb_with']] <- 
   mycells_switching %>% ungroup() %>% 
   filter(!date %in% discarded_dates) %>%
   filter(!discard_arrested) %>% 
   filter(switch_idx==1) %>%
   filter(str_detect(condition, '^switch_[0-9]+h$') |
            str_detect(condition, 'withIPTG') | str_detect(condition, 'lacIoe$')) %>% 
   mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'control', .x)),
          laci=fct_recode(condition, 'lower'='switch_withIPTG5uM', 'lower'='switch_withIPTG1uM',
                          'higher'='switch_lacIoe', 'higher'='switch_lacIoe_withIPTG10uM') %>% 
            fct_relevel('lower', 'control'),
          condition=fct_relevel(condition, 'switch_lacIoe', 'switch_lacIoe_withIPTG10uM', 
                                "control", 'switch_withIPTG1uM', 'switch_withIPTG5uM') %>% 
            fct_recode('with 5µM IPTG'='switch_withIPTG5uM', 'with 1µM IPTG'='switch_withIPTG1uM',
                       'with LacI plasmid'='switch_lacIoe', 
                       'with LacI plasmid\n+ 10µM IPTG'='switch_lacIoe_withIPTG10uM') ) %>% 
   mutate(lag=lag_200,
          lag=ifelse(lag>240*60, Inf, lag)
          #        lag=ifelse(!is.infinite(lag), lag, max(lag[is.finite(lag)])+60),
          #        lag=ifelse(is.na(lag), -200, lag)
   ) %>% 
   (function(.df) 
     ggplot(.df, aes(x=condition, y=lag/60)) +
      geom_hline(yintercept=50, lty='dashed') +
      geom_violin(aes(group=date, fill=laci, col=laci), adjust=0.7, width=1.45, alpha=.4, position='identity') +
      geom_text(aes(y=0, label=annot), vjust=0.5,
                data=.df %>% group_by(condition, laci) %>% 
                  summarise(n=n(), type='short', prop=sum(lag/60<50, na.rm=TRUE)/sum(!is.na(lag)),
                            annot=sprintf("p=%.02f\nn=%d", prop, n)) ) +
      expand_limits(y=-10) +
      labs(y=lac_lags_label, col='LacI activity', fill='LacI activity') +
      guides(fill=guide_legend(override.aes=list(alpha=1))) +
      theme_cowplot_legend_inset(0.8) +
      theme(axis.title.x=element_blank(),
            axis.text.x=element_text(size=rel(1), colour='black',
                                     angle=45, hjust=0.8, vjust=0.9),
            legend.margin=margin(),
            legend.position='top') +
      NULL
   ))

Importantly, we perturb the basal expression such that the lac promoter is not induced before the switch. xxx add induced levels

(myplots[['basal_perturb_gfp']] <- 
   mycells_switching %>% ungroup() %>% 
   filter(!date %in% discarded_dates) %>%
   filter(!discard_arrested) %>% 
   filter(switch_idx==1) %>%
   filter(str_detect(condition, '^switch_[0-9]+h$') | str_detect(condition, 'preIPTG') ) %>% 
   mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'control', .x)),
          laci=fct_recode(condition, 'lower'='switch_preIPTG5uM', 'higher'='switch_lacIoe_preIPTG10uM') %>% 
            fct_relevel('lower', 'control'),
          condition=fct_relevel(condition, 'switch_lacIoe', 'switch_lacIoe_preIPTG10uM', 
                                "control", 'switch_preIPTG5uM') %>% 
            fct_recode('with 5µM IPTG'='switch_preIPTG5uM', 'no IPTG'='control',
                       'with LacI plasmid\n+ 10µM IPTG'='switch_lacIoe_preIPTG10uM') ) %>% 
   mutate(lag=lag_200,
          lag=ifelse(!is.infinite(lag), lag, max(lag[is.finite(lag)])+60),
          lag=ifelse(is.na(lag), -200, lag)) %>% 
   ggplot(aes(condition, gfp_ini)) +
   geom_violin(aes(group=date, col=laci, fill=laci), adjust=0.7, alpha=.4, position='identity') +
   labs(x='', y='LacZ-GFP at the switch (molecules / cell)', col='LacI activity', fill='LacI activity') +
   guides(fill=guide_legend(override.aes=list(alpha=1))) +
   theme(axis.title.x=element_blank(),
         axis.text.x=element_text(size=rel(1), colour='black',
                                  angle=45, hjust=0.8, vjust=0.9),
         legend.margin=margin(),
         legend.title=element_text(size=rel(0.7), face='bold'),
         legend.text=element_text(size=rel(0.7)),
         legend.position='top') +
   NULL
)

Perturbing repressed expression with different media

Another prediction of our interpretation is that the proportion of short lag should increase when cells are switched from conditions where the basal expresion of the lac operon is higher than in M9 + 0.2% glucose:

  • in M9 + 0.4% glycerol, cells grow slower and CRP is active which is expecetd to produce more frequent bursts of spontaneous lac epxression
  • in M9 + 0.2% glucose + 0.2% lactose (which is the initial condition used in classical diauxie experiments), the fraction of fast switching cells is also increased which we interpret as each burst being larger (since it can be transiently amplified by teh presence of lactose which is an inducer; note that the cells are still kept off mostly due to inducer exclusion)
(myplots[['lags_hist_glyc_glcLac']] <- 
   mycells_switching %>% ungroup %>% 
   filter(! date %in% discarded_dates) %>% 
   filter(!discard_arrested | condition=='switch_gly_lac') %>%
   filter(switch_idx==1) %>% 
   filter(str_detect(condition, '^switch_[0-9]+h$') | condition %in% c('switch_glcLac_lac', 'switch_gly_lac') ) %>%
   mutate(lag=lag_200, 
          lag=ifelse(is.na(lag), -1000, lag) ) %>% 
   mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'control', .x)),
          condition=fct_recode(factor(condition), 'glucose + lactose > lactose'='switch_glcLac_lac',
                               'glycerol > lactose'='switch_gly_lac',
                               'glucose > lactose (control)'='control') ) %>%
   ggplot(aes(lag/60, ..density..)) +
   geom_freqpoly(aes(col=condition), binwidth=6) +
   coord_cartesian(xlim=c(0, 240)) +
   labs(x=lac_lags_label) +
   NULL
) +
  labs(subtitle='naive cells')

# mycells_switching %>% ungroup %>% 
#   filter(! date %in% discarded_dates) %>% 
#   filter(!discard_arrested | condition=='switch_gly_lac') %>%
#   filter(switch_idx==1) %>% 
#   filter(str_detect(condition, '^switch_[0-9]+h$') | condition %in% c('switch_glcLac_lac', 'switch_gly_lac') ) %>%
#   mutate(lag=lag_200) %>% 
#   mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'control', .x)),
#          condition=fct_recode(factor(condition), 'glucose + lactose > lactose'='switch_glcLac_lac',
#                               'glycerol > lactose'='switch_gly_lac',
#                               'glucose > lactose (control)'='control') ) %>%
#   group_by(condition) %>% 
#   summarise(n=n(), p_short=sum(lag/60<50, na.rm=TRUE)/sum(!is.na(lag)), p_short_se=sqrt(p_short*(1-p_short)/n)) %>% 
#   ggplot(aes(x=condition, y=p_short)) +
#   geom_bar(stat='identity') +
#   geom_errorbar(aes(ymin=p_short-p_short_se, ymax=p_short+p_short_se), width=.4) +
#   geom_text(aes(y=0.65, label=sprintf('n=%d', n)), vjust=1.1) +
#   # coord_cartesian(xlim=c(0, 240)) +
#   labs(y='fraction of short lags') +
#   theme(axis.text.x=element_text(size=rel(1), colour='black'), axis.title.x=element_blank()) +
#   NULL


(myplots[['glyc_mix_violin']] <- 
   mycells_switching %>% ungroup %>% 
   filter(! date %in% discarded_dates) %>% 
   filter(!discard_arrested | condition=='switch_gly_lac') %>%
   filter(switch_idx==1) %>% 
   filter(str_detect(condition, '^switch_[0-9]+h$') | condition %in% c('switch_glcLac_lac', 'switch_gly_lac') ) %>%
   mutate(lag=lag_200) %>% 
   mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'control', .x)),
          # condition=fct_recode(factor(condition), 'glucose + lactose > lactose'='switch_glcLac_lac',
          #                      'glycerol > lactose'='switch_gly_lac',
          #                      'glucose > lactose (control)'='control') ) %>%
          # laci=ifelse(condition=='control', 'control', NA),
          condition=fct_recode(factor(condition), 'gluc + lac \u2794 lac'='switch_glcLac_lac',
                               'glyc \u2794 lac'='switch_gly_lac',
                               'gluc \u2794 lac'='control',
                               ) ) %>%
   mutate(lag=lag_200,
          lag=ifelse(lag>240*60, Inf, lag)
   ) %>% 
   (function(.df)
     ggplot(.df, aes(x=condition, y=lag/60)) +
      geom_hline(yintercept=50, lty='dashed') +
      geom_violin(aes(group=date, fill=condition, col=condition), adjust=.7, width=1.1, alpha=.4, position='identity') +
      geom_text(aes(y=-10, label=annot), vjust=0.5, size=3, lineheight=1,
                data=.df %>% group_by(condition) %>% 
                  summarise(n=n(), type='short', prop=sum(lag/60<50, na.rm=TRUE)/sum(!is.na(lag)),
                            annot=sprintf("%.0f%%\nn=%d", prop*100, n)) ) +
      coord_cartesian(ylim=c(-35, 240), expand=FALSE) +
      labs(y=lac_lags_label) +
      scale_colour_manual(values=qual_cols[c(2,1,3)]) +
      scale_fill_manual(values=qual_cols[c(2,1,3)]) +
      # scale_colour_discrete(palette=function(.n) qual_cols[c(2,1,3)]) +
      # scale_fill_discrete(limits=c('lower', 'control', 'higher')) +
      # guides(fill=guide_legend(override.aes=list(alpha=1))) +
      guides(col='none', fill='none') +
      theme_cowplot_legend_inset(0.8) +
      theme(axis.title.x=element_blank(),
            axis.text.x=element_text(size=rel(1), colour='black',
                                     angle=45, hjust=0.8, vjust=0.9),
            legend.margin=margin(),
            legend.position='top') +
      NULL
   ))

(myplots[['glyc_mix_frac_short']] <- 
   mycells_switching %>% ungroup %>% 
   filter(! date %in% discarded_dates) %>% 
   filter(!discard_arrested | condition=='switch_gly_lac') %>%
   filter(switch_idx==1) %>% 
   filter(str_detect(condition, '^switch_[0-9]+h$') | condition %in% c('switch_glcLac_lac', 'switch_gly_lac') ) %>%
   mutate(lag=lag_200) %>% 
   mutate(condition=fct_relabel(condition, function(.x) ifelse(str_detect(.x, '^switch_[0-9]+h$'), 'control', .x)),
          # condition=fct_recode(factor(condition), 'glucose + lactose > lactose'='switch_glcLac_lac',
          #                      'glycerol > lactose'='switch_gly_lac',
          #                      'glucose > lactose (control)'='control') ) %>%
          laci=ifelse(condition=='control', 'control', NA),
          condition=fct_recode(factor(condition), 'gluc + lac \u2794 lac'='switch_glcLac_lac',
                               'glyc \u2794 lac'='switch_gly_lac',
                               'gluc \u2794 lac (control)'='control') ) %>%
   group_by(condition, laci) %>% 
   summarise(n=n(), type='short', prop=sum(lag/60<50, na.rm=TRUE)/sum(!is.na(lag)), 
             prop_se=sqrt(prop*(1-prop)/n)) %>% 
   ggplot(aes(x=condition, y=prop)) +
   geom_bar(aes(y=1), fill='gray95', stat='identity') +
   geom_bar(aes(fill=laci), stat='identity') +
   # geom_rect(aes(xmin=as.numeric(condition)-.45, xmax=as.numeric(condition)+.45, 
   #               ymin=prop-prop_se, ymax=prop+prop_se), data=.df) +
   geom_errorbar(aes(ymin=prop-prop_se, ymax=prop+prop_se), width=.25) +
   geom_text(aes(y=1, label=sprintf('n=%d', n)), vjust=1.3) +
   labs(y='prop. of short lags') +
   # expression(paste("type of ", italic("lac"), " induction lag"))
    scale_fill_discrete(limits=c('lower', 'control', 'higher')) +
    theme_cowplot_legend_inset(0.8) +
    theme(axis.title.x=element_blank(),
          axis.text.x=element_text(size=rel(1), colour='black',
                                   angle=45, hjust=0.8, vjust=0.9),
          legend.margin=margin(),
          legend.position='top') +
    NULL
)

NB: 2 independent replicates per condition