from big_ol_pile_of_manim_imports import *

from scene.scene import ProgressDisplay
import scipy

#revert_to_original_skipping_status

def get_binomial_distribution(n, p):
    return lambda k : choose(n, k)*(p**(k))*((1-p)**(n-k))

def get_quiz(*questions):
    q_mobs = VGroup(*list(map(TextMobject, [
        "%d. %s"%(i+1, question)
        for i, question in enumerate(questions)
    ])))
    q_mobs.arrange(
        DOWN, 
        buff = MED_LARGE_BUFF,
        aligned_edge = LEFT, 
    )
    content = VGroup(
        TextMobject("Quiz").scale(1.5),
        Line(q_mobs.get_left(), q_mobs.get_right()),
        q_mobs
    )
    content.arrange(DOWN, buff = MED_SMALL_BUFF)
    rect = SurroundingRectangle(content, buff = MED_LARGE_BUFF)
    rect.shift(MED_SMALL_BUFF*DOWN)
    rect.set_color(WHITE)
    quiz = VGroup(rect, content)
    quiz.questions = q_mobs
    quiz.scale(0.7)
    return quiz

class Checkmark(TexMobjectFromPresetString):
    CONFIG = {
        "tex" : "\\checkmark",
        "color" : GREEN
    }

class Xmark(TexMobjectFromPresetString):
    CONFIG = {
        "tex" : "\\times",
        "color" : RED
    }



def get_slot_group(
    bool_list, 
    buff = MED_LARGE_BUFF, 
    include_qs = True,
    min_bool_list_len = 3,
    ):
    if len(bool_list) < min_bool_list_len:
        bool_list += [None]*(min_bool_list_len - len(bool_list))
    n = len(bool_list)

    lines = VGroup(*[
        Line(ORIGIN, MED_LARGE_BUFF*RIGHT)
        for x in range(n)
    ])
    lines.arrange(RIGHT, buff = buff)
    if include_qs:
        labels = VGroup(*[
            TextMobject("Q%d"%d) for d in range(1, n+1)
        ])
    else:
        labels = VGroup(*[VectorizedPoint() for d in range(n)])
    for label, line in zip(labels, lines):
        label.scale(0.7)
        label.next_to(line, DOWN, SMALL_BUFF)
    slot_group = VGroup()
    slot_group.lines = lines
    slot_group.labels = labels
    slot_group.content = VGroup()
    slot_group.digest_mobject_attrs()
    slot_group.to_edge(RIGHT)
    slot_group.bool_list = bool_list

    total_height = FRAME_Y_RADIUS
    base = 2.3

    for i, line in enumerate(lines):
        if i >= len(bool_list) or bool_list[i] is None:
            mob = VectorizedPoint()
        elif bool_list[i]:
            mob = TexMobject("\\checkmark")
            mob.set_color(GREEN)
            slot_group.shift(total_height*DOWN / (base**(i+1)))
        else:
            mob = TexMobject("\\times")
            mob.set_color(RED)
            slot_group.shift(total_height*UP / (base**(i+1)))
        mob.next_to(line, UP, SMALL_BUFF)
        slot_group.content.add(mob)
    return slot_group

def get_probability_of_slot_group(bool_list, conditioned_list = None):
    filler_tex = "Fi"*max(len(bool_list), 3)
    if conditioned_list is None:
        result = TexMobject("P(", filler_tex, ")")
    else:
        result = TexMobject("P(", filler_tex, "|", filler_tex, ")")
    fillers = result.get_parts_by_tex(filler_tex)
    for filler, bl in zip(fillers, [bool_list, conditioned_list]):
        slot_group = get_slot_group(
            bl, buff = SMALL_BUFF, include_qs = False,
        )
        slot_group.replace(filler, dim_to_match = 0)
        slot_group.shift(0.5*SMALL_BUFF*DOWN)
        index = result.index_of_part(filler)
        result.submobjects[index] = slot_group
    return result


#########

class IndependenceOpeningQuote(OpeningQuote):
    CONFIG = {
        "quote" : [
            "Far better an ", "approximate", 
            " answer to the ", " right question",
            ", which is often vague, than an ", "exact",
            " answer to the ", "wrong question", "."
        ],
        "highlighted_quote_terms" : {
            "approximate" : GREEN,
            "right" : GREEN,
            "exact" : RED,
            "wrong" : RED,
        },
        "author" : "John Tukey",
        "quote_arg_separator" : "",
    }

class DangerInProbability(Scene):
    def construct(self):
        warning = self.get_warning_sign()
        probability = TextMobject("Probability")
        probability.scale(2)

        self.play(Write(warning, run_time = 1))
        self.play(
            warning.next_to, probability, UP, LARGE_BUFF,
            LaggedStartMap(FadeIn, probability)
        )
        self.wait()


    #####

    def get_warning_sign(self):
        triangle = RegularPolygon(n = 3, start_angle = np.pi/2)
        triangle.set_stroke(RED, 12)
        triangle.set_height(2)
        bang = TextMobject("!")
        bang.set_height(0.6*triangle.get_height())
        bang.move_to(interpolate(
            triangle.get_bottom(),
            triangle.get_top(),
            0.4,
        ))
        triangle.add(bang)
        return triangle

class MeaningOfIndependence(SampleSpaceScene):
    CONFIG = {
        "sample_space_config" : {
            "height" : 4,
            "width" : 4,
        }
    }
    def construct(self):
        self.add_labeled_space()
        self.align_conditionals()
        self.relabel()
        self.assume_independence()
        self.no_independence()    

    def add_labeled_space(self):
        self.add_sample_space(**self.sample_space_config)
        self.sample_space.shift(2*LEFT)
        self.sample_space.divide_horizontally(0.3)
        self.sample_space[0].divide_vertically(
            0.9, colors = [BLUE_D, GREEN_C]
        )
        self.sample_space[1].divide_vertically(
            0.5, colors = [BLUE_E, GREEN_E]
        )
        side_braces_and_labels = self.sample_space.get_side_braces_and_labels(
            ["P(A)", "P(\\overline A)"]
        )
        top_braces_and_labels, bottom_braces_and_labels = [
            part.get_subdivision_braces_and_labels(
                part.vertical_parts,
                labels = ["P(B | %s)"%s, "P(\\overline B | %s)"%s],
                direction = vect
            )
            for part, s, vect in zip(
                self.sample_space.horizontal_parts, 
                ["A", "\\overline A"], 
                [UP, DOWN],
            )
        ]
        braces_and_labels_groups = VGroup(
            side_braces_and_labels,
            top_braces_and_labels,
            bottom_braces_and_labels,
        )

        self.add(self.sample_space)
        self.play(Write(braces_and_labels_groups, run_time = 4))

    def align_conditionals(self):
        line = Line(*[
            interpolate(
                self.sample_space.get_corner(vect+LEFT),
                self.sample_space.get_corner(vect+RIGHT),
                0.7
            )
            for vect in (UP, DOWN)
        ])
        line.set_stroke(RED, 8)
        word = TextMobject("Independence")
        word.scale(1.5)
        word.next_to(self.sample_space, RIGHT, buff = LARGE_BUFF)
        word.set_color(RED)

        self.play(*it.chain(
            self.get_top_conditional_change_anims(0.7),
            self.get_bottom_conditional_change_anims(0.7)
        ))
        self.play(
            ShowCreation(line),
            Write(word, run_time = 1)
        )
        self.wait()

        self.independence_word = word
        self.independence_line = line

    def relabel(self):
        old_labels = self.sample_space[0].vertical_parts.labels
        ignored_braces, new_top_labels = self.sample_space[0].get_top_braces_and_labels(
            ["P(B)", "P(\\overline B)"]
        )
        equation = TexMobject(
            "P(B | A) = P(B)"
        )
        equation.scale(1.5)
        equation.move_to(self.independence_word)

        self.play(
            Transform(old_labels, new_top_labels),
            FadeOut(self.sample_space[1].vertical_parts.labels),
            FadeOut(self.sample_space[1].vertical_parts.braces),
        )
        self.play(
            self.independence_word.next_to, equation, UP, MED_LARGE_BUFF,
            Write(equation)
        )
        self.wait()

        self.equation = equation

    def assume_independence(self):
        everything = VGroup(*self.get_top_level_mobjects())
        morty = Mortimer()
        morty.scale(0.7)
        morty.to_corner(DOWN+RIGHT)
        bubble = ThoughtBubble(direction = RIGHT)
        bubble.pin_to(morty)
        bubble.set_fill(opacity = 0)

        self.play(
            FadeIn(morty),
            everything.scale, 0.5,
            everything.move_to, bubble.get_bubble_center(),
        )
        self.play(
            morty.change, "hooray", everything,
            ShowCreation(bubble)
        )
        self.wait()
        self.play(Blink(morty))
        self.wait()

        self.morty = morty

    def no_independence(self):
        for part in self.sample_space.horizontal_parts:
            part.vertical_parts.labels = None
        self.play(*it.chain(
            self.get_top_conditional_change_anims(0.9),
            self.get_bottom_conditional_change_anims(0.5),
            [
                self.independence_word.fade, 0.7,
                self.equation.fade, 0.7,
                self.morty.change, "confused", self.sample_space,
                FadeOut(self.independence_line)
            ]
        ))
        self.wait()

class IntroduceBinomial(Scene):
    CONFIG = {
        "n" : 8,
        "p" : 0.7,
    }
    def construct(self):
        self.add_title()
        self.add_bar_chart()
        self.add_p_slider()
        self.write_independence_assumption()
        self.play_with_p_value(0.2, 0.5)
        self.cross_out_assumption()
        self.play_with_p_value(0.8, 0.4)
        self.shift_weight_to_tails()


    def add_title(self):
        title = TextMobject("Binomial distribution")
        title.scale(1.3)
        title.to_edge(RIGHT)
        title.shift(2*UP)

        formula = TexMobject(
            "P(X=", "k", ")=", 
            "{n \\choose k}", 
            "p", "^k",
            "(1-", "p", ")", "^{n-", "k}",
            arg_separator = ""
        )
        formula.set_color_by_tex("k", BLUE)
        formula.set_color_by_tex("p", YELLOW)
        choose_part = formula.get_part_by_tex("choose")
        choose_part.set_color(WHITE)
        choose_part[-2].set_color(BLUE)
        formula.next_to(title, DOWN, MED_LARGE_BUFF)

        self.formula = formula
        self.title = title
        self.add(title, formula)

    def add_bar_chart(self):
        n, p = self.n, self.p
        dist = get_binomial_distribution(n, p)
        chart = BarChart(
            [dist(k) for k in range(n+1)],
            bar_names = list(range(n+1)),
        )
        chart.to_edge(LEFT)
        self.bar_chart = chart

        self.play(LaggedStartMap(
            FadeIn, VGroup(*it.chain(*chart)), 
            run_time = 2
        ))

    def add_p_slider(self):
        interval = UnitInterval(color = LIGHT_GREY)
        interval.set_width(4)
        interval.next_to(
            VGroup(self.bar_chart.x_axis, self.bar_chart.y_axis), 
            UP, MED_LARGE_BUFF
        )
        interval.add_numbers(0, 1)
        triangle = RegularPolygon(
            n=3, start_angle = -np.pi/2,
            stroke_width = 0,
            fill_color = YELLOW,
            fill_opacity = 1,
        )
        triangle.set_height(0.25)
        triangle.move_to(interval.number_to_point(self.p), DOWN)
        label = TexMobject("p")
        label.next_to(triangle, UP, SMALL_BUFF)
        label.set_color(triangle.get_color())

        self.p_slider = VGroup(interval, triangle, label)
        self.play(Write(self.p_slider, run_time = 1))

    def play_with_p_value(self, *values):
        for value in values:
            self.change_p(value)
            self.wait()

    def write_independence_assumption(self):
        assumption = TextMobject("Independence assumption")
        assumption.scale(1.2)
        assumption.next_to(self.formula, DOWN, MED_LARGE_BUFF, LEFT)
        assumption.set_color(GREEN_C)

        self.play(Write(assumption, run_time = 2))
        self.wait()

        self.assumption = assumption

    def cross_out_assumption(self):
        cross = Cross(self.assumption)
        cross.set_color(GREY)
        self.bar_chart.save_state()

        self.play(ShowCreation(cross))
        self.play(self.bar_chart.fade, 0.7)
        self.wait(2)
        self.play(self.bar_chart.restore)

    def shift_weight_to_tails(self):
        chart = self.bar_chart
        chart_copy = chart.copy()
        dist = get_binomial_distribution(self.n, self.p)
        values = np.array(list(map(dist, list(range(self.n+1)))))
        values += 0.1
        values /= sum(values)

        old_bars = chart.bars
        old_bars.generate_target()
        new_bars = chart_copy.bars
        for bars, vect in (old_bars.target, LEFT), (new_bars, RIGHT):
            for bar in bars:
                corner = bar.get_corner(DOWN+vect)
                bar.stretch(0.5, 0)
                bar.move_to(corner, DOWN+vect)
        old_bars.target.set_color(RED)
        old_bars.target.fade()

        self.play(
            MoveToTarget(old_bars),
            ReplacementTransform(
                old_bars.copy().set_fill(opacity = 0),
                new_bars
            )
        )
        self.play(
            chart_copy.change_bar_values, values
        )
        self.wait(2)



    #####

    def change_p(self, p):
        interval, triangle, p_label = self.p_slider
        alt_dist = get_binomial_distribution(self.n, p)
        self.play(
            ApplyMethod(
                self.bar_chart.change_bar_values,
                [alt_dist(k) for k in range(self.n+1)],
            ),
            triangle.move_to, interval.number_to_point(p), DOWN,
            MaintainPositionRelativeTo(p_label, triangle)
        )
        self.p = p

class IntroduceQuiz(PiCreatureScene):
    def construct(self):
        self.add_quiz()
        self.ask_about_probabilities()
        self.show_distribution()
        self.show_single_question_probability()

    def add_quiz(self):
        quiz = self.get_example_quiz()
        quiz.next_to(self.randy, UP+RIGHT)

        self.play(
            Write(quiz),
            self.randy.change, "pondering", quiz
        )
        self.wait()

        self.quiz = quiz

    def ask_about_probabilities(self):
        probabilities, abbreviated_probabilities = [
            VGroup(*[
                TexMobject(
                    "P(", s_tex, "=", str(score), ")", rhs
                ).set_color_by_tex_to_color_map({
                    str(score) : YELLOW,
                    "text" : GREEN,
                })
                for score in range(4)
            ])
            for s_tex, rhs in [
                ("\\text{Score}", "= \\, ???"),
                ("\\text{S}", "")
            ]
        ]
        for group in probabilities, abbreviated_probabilities:
            group.arrange(
                DOWN, 
                buff = MED_LARGE_BUFF,
                aligned_edge = LEFT
            )
            group.to_corner(UP+LEFT)

        self.play(
            LaggedStartMap(FadeIn, probabilities, run_time = 3),
            self.quiz.set_height, 0.7*self.randy.get_height(),
            self.quiz.next_to, self.randy, RIGHT,
            self.randy.change, "confused", probabilities
        )
        self.wait()

        self.probabilities = probabilities
        self.abbreviated_probabilities = abbreviated_probabilities

    def show_distribution(self):
        dist = get_binomial_distribution(3, 0.7)
        values = list(map(dist, list(range(4))))
        chart = BarChart(
            values, 
            width = 7,
            bar_names = list(range(4))
        )
        chart.to_edge(RIGHT)
        for short_p, bar in zip(self.abbreviated_probabilities, chart.bars):
            short_p.set_width(1.75*bar.get_width())
            short_p.next_to(bar, UP)

        self.play(
            LaggedStartMap(Write, VGroup(
                *[m for m in chart if m is not chart.bars]
            )),
        )
        self.play(*[
            ReplacementTransform(
                bar.copy().stretch_to_fit_height(0).move_to(bar.get_bottom()),
                bar
            )
            for bar in chart.bars
        ])
        self.play(*[
            ReplacementTransform(p.copy(), short_p)
            for p, short_p in zip(
                self.probabilities,
                self.abbreviated_probabilities,
            )
        ])
        self.wait()

        self.bar_chart = chart

    def show_single_question_probability(self):
        prob = TexMobject(
            "P(", "\\text{Can answer a given question}", ")",
            "= 0.8"
        )
        prob.to_corner(UP+RIGHT)
        prob.set_color_by_tex("text", GREEN)
        rect = SurroundingRectangle(prob, buff = MED_SMALL_BUFF)

        self.play(
            Write(prob),
            self.randy.change, "happy", prob
        )
        self.play(ShowCreation(rect))
        self.wait()

        self.single_question_probability = VGroup(
            prob, rect
        )


    ######

    def create_pi_creature(self):
        randy = Randolph()
        randy.scale(0.7)
        randy.to_corner(DOWN+LEFT)
        self.randy = randy
        return randy

    def get_example_quiz(self):
        return get_quiz(
            "Define ``Brachistochrone'' ",
            "Define ``Tautochrone'' ",
            "Define ``Cycloid'' ",
        )

class BreakDownQuestionPatterns(IntroduceQuiz):
    def construct(self):
        self.add_parts_from_last_scene()
        self.break_down_possibilities()
        self.count_patterns()

    def add_parts_from_last_scene(self):
        self.force_skipping()
        IntroduceQuiz.construct(self)
        self.revert_to_original_skipping_status()

        chart_group = VGroup(
            self.bar_chart,
            self.abbreviated_probabilities
        )
        self.play(
            self.single_question_probability.scale, 0.8,
            self.single_question_probability.to_corner, UP+LEFT,
            chart_group.scale, 0.7, chart_group.get_top(),
            chart_group.to_edge, LEFT,
            FadeOut(self.probabilities)
        )

    def break_down_possibilities(self):
        slot_group_groups = VGroup(*[VGroup() for x in range(4)])
        bool_lists = [[]]
        while bool_lists:
            bool_list = bool_lists.pop()
            slot_group = self.get_slot_group(bool_list)
            slot_group_groups[len(bool_list)].add(slot_group)
            if len(bool_list) < 3:
                bool_lists += [
                    list(bool_list) + [True],
                    list(bool_list) + [False],
                ]

        group_group = slot_group_groups[0]
        self.revert_to_original_skipping_status()
        self.play(Write(group_group, run_time = 1))
        self.wait()
        for new_group_group in slot_group_groups[1:]:
            self.play(Transform(group_group, new_group_group))
            self.wait(2)

        self.slot_groups = slot_group_groups[-1]

    def count_patterns(self):
        brace = Brace(self.slot_groups, LEFT)
        count = TexMobject("2^3 = 8")
        count.next_to(brace, LEFT)

        self.play(
            GrowFromCenter(brace),
            Write(count)
        )
        self.wait()

    #######

    def get_slot_group(self, bool_list):
        return get_slot_group(bool_list, include_qs = len(bool_list) < 3)

class AssociatePatternsWithScores(BreakDownQuestionPatterns):
    CONFIG = {
        "score_group_scale_val" : 0.8,
    }
    def construct(self):
        self.add_slot_groups()
        self.show_score_groups()
        self.think_about_binomial_patterns()

    def add_slot_groups(self):
        self.slot_groups = VGroup(*list(map(
            self.get_slot_group,
            it.product(*[[True, False]]*3)
        )))
        self.add(self.slot_groups)
        self.remove(self.randy)

    def show_score_groups(self):
        score_groups = [VGroup() for x in range(4)]
        scores = VGroup()
        full_score_groups = VGroup()

        for slot_group in self.slot_groups:
            score_groups[sum(slot_group.bool_list)].add(slot_group)
        for i, score_group in enumerate(score_groups):
            score = TextMobject("Score", "=", str(i))
            score.set_color_by_tex("Score", GREEN)
            scores.add(score)
            score_group.organized = score_group.deepcopy()
            score_group.organized.arrange(UP, buff = SMALL_BUFF)
            score_group.organized.scale(self.score_group_scale_val)
            brace = Brace(score_group.organized, LEFT)
            score.next_to(brace, LEFT)
            score.add(brace)
            full_score_groups.add(VGroup(score, score_group.organized))
        full_score_groups.arrange(
            DOWN, buff = MED_LARGE_BUFF,
            aligned_edge = RIGHT
        )
        full_score_groups.to_edge(LEFT)

        for score, score_group in zip(scores, score_groups):
            score_group.save_state()
            self.play(score_group.next_to, score_group, LEFT, MED_LARGE_BUFF)
            self.wait()
            self.play(
                ReplacementTransform(
                    score_group.copy(), score_group.organized
                ),
                LaggedStartMap(FadeIn, score, run_time = 1)
            )
            self.play(score_group.restore)
        self.wait()

    def think_about_binomial_patterns(self):
        triangle = PascalsTriangle(
            nrows = 5,
            height = 3,
            width = 3,
        )
        triangle.to_edge(UP+RIGHT)
        row = VGroup(*[
            triangle.coords_to_mobs[3][k]
            for k in range(4)
        ])
        self.randy.center().to_edge(DOWN)
        bubble = ThoughtBubble()
        bubble.add_content(triangle)
        bubble.resize_to_content()
        triangle.shift(SMALL_BUFF*(3*UP + RIGHT))
        bubble.add(triangle)
        bubble.next_to(self.randy, UP+RIGHT, SMALL_BUFF)
        bubble.remove(triangle)

        self.play(
            FadeOut(self.slot_groups),
            FadeIn(self.randy),
            FadeIn(bubble)
        )
        self.play(
            self.randy.change, "pondering",
            LaggedStartMap(FadeIn, triangle, run_time = 4),
        )
        self.play(row.set_color, YELLOW)
        self.wait(4)

class BeforeCounting(TeacherStudentsScene):
    def construct(self):
        triangle = PascalsTriangle(nrows = 7)
        triangle.set_height(4)
        triangle.next_to(self.teacher, UP+LEFT)

        prob = get_probability_of_slot_group([True, True, False])
        prob.to_edge(UP)
        brace = Brace(prob, DOWN)
        q_marks = brace.get_text("???")

        self.teacher.change_mode("raise_right_hand")
        self.add(triangle)
        self.change_student_modes(*["hooray"]*3)
        self.play(
            triangle.scale, 0.5,
            triangle.to_corner, UP+RIGHT,
            self.teacher.change_mode, "sassy"
        )
        self.change_student_modes(*["confused"]*3)
        self.play(Write(prob))
        self.play(
            GrowFromCenter(brace),
            LaggedStartMap(FadeIn, q_marks)
        )
        self.wait(2)

class TemptingButWrongCalculation(BreakDownQuestionPatterns):
    def construct(self):
        self.add_title()
        self.write_simple_product()

    def add_title(self):
        title = TextMobject("Tempting$\\dots$")
        title.scale(1.5)
        title.to_edge(UP)
        self.add(title)
        self.title = title

    def write_simple_product(self):
        lhs = TexMobject("P\\big(", "Filler Blah", "\\big)", "= ")
        lhs.next_to(ORIGIN, UP+LEFT)
        p_of = lhs.get_part_by_tex("P\\big(")
        filler = lhs.get_part_by_tex("Filler")
        rp = lhs.get_part_by_tex("\\big)")
        slot_group = self.get_slot_group([True, True, False])
        slot_group.replace(filler, dim_to_match = 0)
        lhs.submobjects.remove(filler)

        rhs = VGroup(*[
            TexMobject("P(", "\\checkmark" if b else "\\times", ")")
            for b in slot_group.bool_list
        ])
        rhs.arrange(RIGHT, SMALL_BUFF)
        rhs.next_to(lhs, RIGHT, SMALL_BUFF)
        for part, b in zip(rhs, slot_group.bool_list):
            part.set_color_by_tex_to_color_map({
                "checkmark" : GREEN,
                "times" : RED,
            })
            brace = Brace(part, UP)
            if b:
                value = TexMobject("(0.8)")
            else:
                value = TexMobject("(0.2)")
            value.set_color(part[1].get_color())
            value.next_to(brace, UP)
            part.brace = brace
            part.value = value

        question = TextMobject("What about correlations?")
        question.next_to(rhs, DOWN, LARGE_BUFF)

        self.play(
            Write(lhs),
            ShowCreation(slot_group.lines),
            LaggedStartMap(FadeIn, slot_group.content, run_time = 3),
            self.randy.change, "pondering"
        )
        self.wait(2)
        for part, mob in zip(rhs, slot_group.content):
            self.play(*[
                ReplacementTransform(
                    mob.copy(), subpart,
                    path_arc = np.pi/6
                )
                for subpart, mob in zip(part, [
                    p_of, mob, rp
                ])
            ])
            self.play(GrowFromCenter(part.brace))
            self.play(FadeIn(part.value))
            self.wait()
        self.wait()
        self.play(
            Write(question),
            self.randy.change, "confused"
        )
        self.wait(3)

        self.question = question
        self.rhs = rhs

class ThousandPossibleQuizzes(Scene):
    CONFIG = {
        "n_quiz_rows" : 25,
        "n_quiz_cols" : 40,
        "n_movers" : 100,
        # "n_quiz_rows" : 5,
        # "n_quiz_cols" : 8,
        # "n_movers" : 4,
        "quizzes_height" : 4,
    }
    def construct(self):
        self.draw_all_quizzes()
        self.show_division_by_first_question()
        self.ask_about_second_question()
        self.show_uncorrelated_division_by_second()
        self.increase_second_correct_slice()
        self.second_division_among_first_wrong()
        self.show_that_second_is_still_80()
        self.emphasize_disproportionate_divide()
        self.show_third_question_results()

    def draw_all_quizzes(self):
        quizzes = self.get_thousand_quizzes()
        title = TextMobject("$1{,}000$ possible quizzes")
        title.scale(1.5)
        title.next_to(quizzes, UP)
        full_quizzes = VGroup(
            get_quiz(
                "Define ``Brachistochrone''",
                "Define ``Tautochrone''",
                "Define ``Cycloid''",
            ),
            get_quiz(
                "Define $\\dfrac{df}{dx}$",
                "Define $\\displaystyle \\lim_{h \\to 0} f(h)$",
                "Prove $\\dfrac{d(x^2)}{dx} = 2x$ ",
            ),
            get_quiz(
                "Find all primes $p$ \\\\ where $p+2$ is prime.",
                "Find all primes $p$ \\\\ where $2^{p}-1$ is prime.",
                "Solve $\\zeta(s) = 0$",
            ),
        )
        full_quizzes.arrange(RIGHT)
        target_quizzes = VGroup(*quizzes[:len(full_quizzes)])

        for quiz in full_quizzes:
            self.play(FadeIn(quiz, run_time = 3, lag_ratio = 0.5))
        self.play(
            Transform(full_quizzes, target_quizzes),
            FadeIn(title)
        )
        self.play(
            LaggedStartMap(
                FadeIn, quizzes, 
                run_time = 3,
                lag_ratio = 0.2,
            ),
            Animation(full_quizzes, remover = True)
        )
        self.wait()

        self.quizzes = quizzes
        self.title = title

    def show_division_by_first_question(self):
        n = int(0.8*len(self.quizzes))
        top_split = VGroup(*self.quizzes[:n])
        bottom_split = VGroup(*self.quizzes[n:])
        for split, color, vect in (top_split, GREEN, UP), (bottom_split, RED, DOWN):
            split.sort(lambda p : p[0])
            split.generate_target()
            split.target.shift(MED_LARGE_BUFF*vect)
            for quiz in split.target:
                quiz[0].set_color(color)

        labels = VGroup()
        for num, b, split in (800, True, top_split), (200, False, bottom_split):
            label = VGroup(
                TexMobject(str(num)),
                get_slot_group([b], buff = SMALL_BUFF, include_qs = False)
            )
            label.arrange(DOWN)
            label.next_to(split.target, LEFT, buff = LARGE_BUFF)
            labels.add(label)

        self.play(
            FadeOut(self.title),
            MoveToTarget(top_split),
            MoveToTarget(bottom_split),
        )
        for label in labels:
            self.play(FadeIn(label))
            self.wait()

        self.splits = VGroup(top_split, bottom_split)
        self.q1_split_labels = labels

    def ask_about_second_question(self):
        top_split = self.splits[0]
        sg1, sg2 = slot_groups = VGroup(*[
            get_slot_group(
                [True, b], 
                include_qs = False,
                buff = SMALL_BUFF
            )
            for b in (True, False)
        ])
        question = VGroup(
            TextMobject("Where are"), sg1,
            TextMobject("and"), sg2, TextMobject("?"),
        )
        question.arrange(RIGHT, aligned_edge = DOWN)
        question[-1].next_to(question[-2], RIGHT, SMALL_BUFF)
        question.next_to(top_split, UP, MED_LARGE_BUFF)
        slot_groups.shift(SMALL_BUFF*DOWN)
        little_rects = VGroup(*[
            SurroundingRectangle(
                VGroup(sg.lines[1], sg.content[1])
            )
            for sg in slot_groups
        ])
        big_rect = SurroundingRectangle(top_split)

        self.play(Write(question))
        self.play(ShowCreation(little_rects))
        self.wait()
        self.play(FadeOut(little_rects))
        self.play(ShowCreation(big_rect))
        self.play(
            FadeOut(big_rect),
            FadeOut(question),
        )
        self.wait()

    def show_uncorrelated_division_by_second(self):
        top_split = self.splits[0]
        top_label = self.q1_split_labels[0]
        n = int(0.8*len(top_split))
        left_split = VGroup(*top_split[:n])
        right_split = VGroup(*top_split[n:])
        for split, color in (left_split, GREEN_E), (right_split, RED_E):
            split.generate_target()
            for quiz in split.target:
                quiz[1].set_color(color)
        left_split.target.shift(LEFT)

        left_label = VGroup(
            TexMobject("(0.8)", "800 =", "640"),
            get_slot_group([True, True], buff = SMALL_BUFF, include_qs = False)
        )
        left_label.arrange(RIGHT, buff = MED_LARGE_BUFF)
        left_label.next_to(left_split.target, UP)

        self.play(
            MoveToTarget(left_split),
            MaintainPositionRelativeTo(top_label, left_split),
            MoveToTarget(right_split),
        )
        self.play(FadeIn(left_label))
        self.play(LaggedStartMap(
            ApplyMethod, left_split,
            lambda m : (m.set_color, YELLOW),
            rate_func = there_and_back,
            lag_ratio = 0.2,
        ))
        self.wait()

        self.top_left_label = left_label
        self.top_splits = VGroup(left_split, right_split)

    def increase_second_correct_slice(self):
        left_split, right_split = self.top_splits
        left_label = self.top_left_label
        left_label_equation = left_label[0]
        movers = VGroup(*right_split[:self.n_movers])
        movers.generate_target()
        for quiz in movers.target:
            quiz[1].set_color(left_split[0][1].get_color())
        movers.target.shift(LEFT)

        new_equation = TexMobject("(0.925)", "800 =", "740")
        for i in 0, 2:
            new_equation[i].set_color(YELLOW)
        new_equation.move_to(left_label_equation)

        self.play(
            MoveToTarget(
                movers, 
                lag_ratio = 0.5,
                run_time = 3,
            ),
            Transform(left_label_equation, new_equation)
        )
        self.wait(2)
        self.play(Indicate(left_label_equation[0]))
        self.wait()

        left_split.add(*movers)
        right_split.remove(*movers)
        self.top_left_split = left_split
        self.top_right_split = right_split
        self.top_movers = movers
        self.top_equation = left_label_equation

    def second_division_among_first_wrong(self):
        top_label, bottom_label = self.q1_split_labels
        top_split, bottom_split = self.splits
        top_left_label = self.top_left_label
        top_group = VGroup(top_split, top_left_label, top_label)

        n = int(0.8*len(bottom_split))
        left_split = VGroup(*bottom_split[:n])
        right_split = VGroup(*bottom_split[n:])
        for split, color in (left_split, GREEN_E), (right_split, RED_E):
            split.generate_target()
            for quiz in split.target:
                quiz[1].set_color(color)
        left_split.target.shift(LEFT)

        movers = VGroup(*left_split[-self.n_movers:])
        movers.generate_target()
        for quiz in movers.target:
            quiz[1].set_color(right_split.target[0][1].get_color())

        equation = TexMobject("(0.8)", "200 = ", "160")
        slot_group = get_slot_group([False, True], buff = SMALL_BUFF, include_qs = False)
        label = VGroup(equation, slot_group)
        label.arrange(DOWN, buff = SMALL_BUFF)
        label.next_to(left_split.target, UP, SMALL_BUFF, LEFT)
        alt_equation = TexMobject("(0.3)", "200 = ", "60")
        for i in 0, 2:
            alt_equation[i].set_color(YELLOW)
        alt_equation.move_to(equation)

        self.play(top_group.to_edge, UP, SMALL_BUFF)
        self.play(
            bottom_label.shift, LEFT,
            *list(map(MoveToTarget, [left_split, right_split]))
        )
        self.play(FadeIn(label))
        self.wait()
        self.play(
            MoveToTarget(
                movers, 
                lag_ratio = 0.5,
                run_time = 3,
            ),
            Transform(equation, alt_equation)
        )
        self.wait()

        left_split.remove(*movers)
        right_split.add(*movers)
        self.bottom_left_split = left_split
        self.bottom_right_split = right_split
        self.bottom_movers = movers
        self.bottom_equation = equation
        self.bottom_left_label = label

    def show_that_second_is_still_80(self):
        second_right = VGroup(
            self.bottom_left_split, self.top_left_split
        )
        second_wrong = VGroup(
            self.bottom_right_split, self.top_right_split
        )
        rects = VGroup(*[
            SurroundingRectangle(mob, buff = SMALL_BUFF)
            for mob in second_right
        ])

        num1 = self.top_equation[-1].copy()
        num2 = self.bottom_equation[-1].copy()

        equation = TexMobject("740", "+", "60", "=", "800")
        for tex in "740", "60":
            equation.set_color_by_tex(tex, YELLOW)
        slot_group = get_slot_group([True, True])
        slot_group.content[0].set_fill(BLACK, 0)
        label = VGroup(equation, slot_group)
        label.arrange(DOWN)
        label.next_to(self.quizzes, LEFT, LARGE_BUFF)

        self.play(
            FadeOut(self.q1_split_labels),
            ShowCreation(rects)
        )
        self.play(
            FadeIn(slot_group),
            Transform(
                num1, equation[0],
                rate_func = squish_rate_func(smooth, 0, 0.7),
            ),
            Transform(
                num2, equation[2],
                rate_func = squish_rate_func(smooth, 0.3, 1),
            ),
            run_time = 2
        )
        self.play(
            Write(equation),
            *list(map(Animation, [num1, num2]))
        )
        self.remove(num1, num2)
        self.wait()
        self.play(FadeOut(rects))

    def emphasize_disproportionate_divide(self):
        top_movers = self.top_movers
        bottom_movers = self.bottom_movers
        both_movers = VGroup(top_movers, bottom_movers)
        both_movers.save_state()

        top_movers.target = bottom_movers.copy().shift(LEFT)
        bottom_movers.target = top_movers.copy().shift(RIGHT)
        for quiz in top_movers.target:
            quiz[0].set_color(RED)
        for quiz in bottom_movers.target:
            quiz[0].set_color(GREEN)

        line = Line(UP, DOWN, color = YELLOW)
        line.set_height(self.quizzes.get_height())
        line.next_to(bottom_movers.target, LEFT, MED_LARGE_BUFF, UP)

        self.revert_to_original_skipping_status()
        self.play(*list(map(MoveToTarget, both_movers)))
        self.play(ShowCreation(line))
        self.play(FadeOut(line))
        self.wait()
        self.play(both_movers.restore)
        self.wait()

    def show_third_question_results(self):
        all_splits = VGroup(
            self.top_left_split, self.top_right_split,
            self.bottom_left_split, self.bottom_right_split
        )
        proportions = [0.9, 0.8, 0.8, 0.4]
        for split, prop in zip(all_splits, proportions):
            n = int(prop*len(split))
            split.sort(lambda p : -p[1])
            split.generate_target()
            top_part = VGroup(*split.target[:n])
            top_part.shift(MED_SMALL_BUFF*UP)
            bottom_part = VGroup(*split.target[n:])
            bottom_part.shift(MED_SMALL_BUFF*DOWN)
            for quiz in top_part:
                quiz[-1].set_color(GREEN)
            for quiz in bottom_part:
                quiz[-1].set_color(RED)

        split = self.top_left_split
        n_all_right = int(proportions[0]*len(split))
        all_right = VGroup(*split[:n_all_right])

        self.play(
            FadeOut(self.top_left_label),
            FadeOut(self.bottom_left_label),
        )
        for split in all_splits:
            self.play(MoveToTarget(split))
        self.wait()
        self.play(LaggedStartMap(
            ApplyMethod, all_right,
            lambda m : (m.set_color, YELLOW),
            rate_func = there_and_back,
            lag_ratio = 0.2,
            run_time = 2
        ))
        self.wait(2)

    #####

    def get_thousand_quizzes(self):
        rows = VGroup()
        for x in range(self.n_quiz_rows):
            quiz = VGroup(*[
                Rectangle(
                    height = SMALL_BUFF,
                    width = 0.5*SMALL_BUFF
                )
                for x in range(3)
            ])
            quiz.arrange(RIGHT, buff = 0)
            quiz.set_stroke(width = 0)
            quiz.set_fill(LIGHT_GREY, 1)
            row = VGroup(*[quiz.copy() for y in range(self.n_quiz_cols)])
            row.arrange(RIGHT, buff = SMALL_BUFF)
            rows.add(row)

        rows.arrange(DOWN, buff = SMALL_BUFF)
        quizzes = VGroup(*it.chain(*rows))
        quizzes.set_height(self.quizzes_height)
        quizzes.to_edge(RIGHT)
        quizzes.shift(MED_LARGE_BUFF*DOWN)
        return quizzes

class ExampleConditional(Scene):
    def construct(self):
        prob = get_probability_of_slot_group(
            [True, True], [True]
        )
        rhs = TexMobject("=", "0.925", ">", "0.8")
        rhs.set_color_by_tex("0.925", YELLOW)
        rhs.next_to(prob, RIGHT)
        expression = VGroup(prob, rhs)
        expression.set_width(FRAME_WIDTH - 1)
        expression.center().to_edge(DOWN)

        self.play(Write(expression))
        self.wait()

class HarderQuizzes(Scene):
    def construct(self):
        quizzes = VGroup(
            get_quiz(
                "Find all primes $p$ \\\\ where $p+2$ is prime.",
                "Find all primes $p$ \\\\ where $2^{p}-1$ is prime.",
                "Solve $\\zeta(s) = 0$",
            ),
            get_quiz(
                "Find $S$ such that \\\\ $\\#\\mathds{N} < \\#S < \\#\\mathcal{P}(\\mathds{N})$",
                "Describe ``forcing''",
                "Prove from ZFC that $S \\notin S$.",
            ),
        )
        quizzes.arrange(RIGHT)
        quizzes.to_edge(DOWN)
        crosses = VGroup(*[
            Cross(quiz.questions[0])
            for quiz in quizzes
        ])

        for quiz in quizzes:
            self.play(FadeIn(quiz))
        self.wait()
        for cross in crosses:
            self.play(ShowCreation(cross))
        self.wait()

class WritePSecond(Scene):
    def construct(self):
        prob = get_probability_of_slot_group([None, True, None])
        rhs = TexMobject("= 0.8")
        rhs.next_to(prob, RIGHT)
        prob.add(rhs)
        prob.set_width(FRAME_WIDTH - 1)
        prob.center().to_edge(DOWN)
        self.play(Write(prob))

class SubmitToTemptation(TemptingButWrongCalculation):
    def construct(self):
        self.force_skipping()
        TemptingButWrongCalculation.construct(self)
        self.revert_to_original_skipping_status()

        title = self.title
        question = self.question
        title.generate_target()
        title.target.scale_in_place(1./1.5)
        new_words = TextMobject("and", "okay", "assuming independence.")
        new_words.set_color_by_tex("okay", GREEN)
        new_words.next_to(title.target, RIGHT)
        VGroup(title.target, new_words).center().to_edge(UP)

        self.play(
            MoveToTarget(title),
            FadeOut(question)
        )
        self.play(
            Write(new_words, run_time = 2),
            self.randy.change, "hooray"
        )
        for part in self.rhs:
            self.play(Indicate(part.value))
        self.wait()

class AccurateProductRule(SampleSpaceScene, ThreeDScene):
    def construct(self):
        self.setup_terms()
        self.add_sample_space()
        self.wait()
        self.show_first_division()
        self.show_second_division()
        self.move_to_third_dimension()
        self.show_final_probability()
        self.show_confusion()

    def setup_terms(self):
        filler_tex = "Filler"
        lhs = TexMobject("P(", filler_tex, ")", "=")
        p1 = TexMobject("P(", filler_tex, ")")
        p2 = TexMobject("P(", filler_tex, "|", filler_tex, ")")
        p3 = TexMobject("P(", filler_tex, "|", filler_tex, ")")
        terms = VGroup(lhs, p1, p2, p3)
        terms.arrange(RIGHT, buff = SMALL_BUFF)
        terms.to_edge(UP, buff = LARGE_BUFF)

        kwargs = {"buff" : SMALL_BUFF, "include_qs" : False}
        slot_group_lists = [
            [get_slot_group([True, True, False], **kwargs)],
            [get_slot_group([True], **kwargs)],
            [
                get_slot_group([True, True], **kwargs),
                get_slot_group([True], **kwargs),
            ],
            [
                get_slot_group([True, True, False], **kwargs),
                get_slot_group([True, True], **kwargs),
            ],
        ]
        for term, slot_group_list in zip(terms, slot_group_lists):
            parts = term.get_parts_by_tex(filler_tex)
            for part, slot_group in zip(parts, slot_group_list):
                slot_group.replace(part, dim_to_match = 0)
                term.submobjects[term.index_of_part(part)] = slot_group
        # terms[2][1].content[0].set_fill(BLACK, 0)
        # VGroup(*terms[3][1].content[:2]).set_fill(BLACK, 0)

        value_texs = ["0.8", ">0.8", "<0.2"]
        for term, tex in zip(terms[1:], value_texs):
            term.value = TexMobject(tex)
            term.value.next_to(term, UP)

        self.terms = terms
        self.add(terms[0])

    def add_sample_space(self):
        SampleSpaceScene.add_sample_space(self, height = 4, width = 5)
        self.sample_space.to_edge(DOWN)

    def show_first_division(self):
        space = self.sample_space
        space.divide_horizontally(
            [0.8], colors = [GREEN_E, RED_E]
        )
        space.horizontal_parts.fade(0.1)
        top_label = self.terms[1].copy()
        bottom_label = top_label.copy()
        slot_group = get_slot_group([False], buff = SMALL_BUFF, include_qs = False)
        slot_group.replace(bottom_label[1])
        Transform(bottom_label[1], slot_group).update(1)
        braces_and_labels = space.get_side_braces_and_labels(
            [top_label, bottom_label]
        )

        self.play(
            FadeIn(space.horizontal_parts),
            FadeIn(braces_and_labels)
        )
        self.play(ReplacementTransform(
            top_label.copy(), self.terms[1]
        ))
        self.wait()
        self.play(Write(self.terms[1].value))
        self.wait()

        space.add(braces_and_labels)
        self.top_part = space.horizontal_parts[0]

    def show_second_division(self):
        space = self.sample_space
        top_part = self.top_part
        green_red_mix = average_color(GREEN_E, RED_E)
        top_part.divide_vertically(
            [0.9], colors = [GREEN_E, green_red_mix]
        )
        label = self.terms[2].deepcopy()
        braces_and_labels = top_part.get_top_braces_and_labels(
            labels = [label]
        )

        self.play(
            FadeIn(top_part.vertical_parts),
            FadeIn(braces_and_labels)
        )
        self.play(ReplacementTransform(
            label.copy(), self.terms[2]
        ))
        self.wait()
        self.play(Write(self.terms[2].value))
        self.wait()

        space.add(braces_and_labels)
        self.top_left_part = top_part.vertical_parts[0]

    def move_to_third_dimension(self):
        space = self.sample_space
        part = self.top_left_part
        cubes = VGroup(
            Cube(fill_color = RED_E),
            Cube(fill_color = GREEN_E),
        )
        cubes.set_fill(opacity = 0)
        cubes.stretch_to_fit_width(part.get_width())
        cubes.stretch_to_fit_height(part.get_height())
        cubes[1].move_to(part, IN)
        cubes[0].stretch(0.2, 2)
        cubes[0].move_to(cubes[1].get_edge_center(OUT), IN)
        space.add(cubes)

        self.play(
            space.rotate, 0.9*np.pi/2, LEFT,
            space.rotate, np.pi/12, UP,
            space.to_corner, DOWN+RIGHT, LARGE_BUFF
        )
        space.remove(cubes)
        self.play(
            cubes[0].set_fill, None, 1,
            cubes[0].set_stroke, WHITE, 1,
            cubes[1].set_fill, None, 0.5,
            cubes[1].set_stroke, WHITE, 1,
        )
        self.wait()

        self.cubes = cubes

    def show_final_probability(self):
        cube = self.cubes[0]
        face = cube[2]
        points = face.get_anchors()
        line = Line(points[2], points[3])
        line.set_stroke(YELLOW, 8)
        brace = Brace(line, LEFT)
        label = self.terms[3].copy()
        label.next_to(brace, LEFT)

        self.play(
            GrowFromCenter(brace),
            FadeIn(label),
        )
        self.wait()
        self.play(ReplacementTransform(
            label.copy(), self.terms[3]
        ))
        self.wait()

    def show_confusion(self):
        randy = Randolph()
        randy.to_corner(DOWN+LEFT)

        self.play(FadeIn(randy))
        self.play(randy.change, "confused", self.terms)
        self.play(randy.look_at, self.cubes)
        self.play(Blink(randy))
        self.play(randy.look_at, self.terms)
        self.wait()

class ShowAllEightConditionals(Scene):
    def construct(self):
        self.show_all_conditionals()
        self.suggest_independence()

    def show_all_conditionals(self):
        equations = VGroup()
        filler_tex = "Filler"
        for bool_list in it.product(*[[True, False]]*3):
            equation = TexMobject(
                "P(", filler_tex, ")", "=",
                "P(", filler_tex, ")",
                "P(", filler_tex, "|", filler_tex, ")",
                "P(", filler_tex, "|", filler_tex, ")",
            )
            sub_bool_lists = [
                bool_list[:n] for n in (3, 1, 2, 1, 3, 2)
            ]
            parts = equation.get_parts_by_tex(filler_tex)
            for part, sub_list in zip(parts, sub_bool_lists):
                slot_group = get_slot_group(
                    sub_list, 
                    buff = SMALL_BUFF,
                    include_qs = False
                )
                slot_group.replace(part, dim_to_match = 0)
                index = equation.index_of_part(part)
                equation.submobjects[index] = slot_group
            equations.add(equation)
        equations.arrange(DOWN)

        rect = SurroundingRectangle(
            VGroup(*equations[0][7:], *equations[-1][7:]),
            buff = SMALL_BUFF
        )
        rect.shift(0.5*SMALL_BUFF*RIGHT)

        self.play(LaggedStartMap(
            FadeIn, equations,
            run_time = 5,
            lag_ratio = 0.3
        ))
        self.wait()
        self.play(ShowCreation(rect, run_time = 2))
        self.play(FadeOut(rect))
        self.wait()

    def suggest_independence(self):
        full_screen_rect = FullScreenFadeRectangle()
        randy = Randolph()
        randy.to_corner(DOWN+LEFT)


        self.play(
            FadeIn(full_screen_rect),
            FadeIn(randy)
        )
        self.play(PiCreatureSays(
            randy, "Let's just assume \\\\ independence.",
            target_mode = "shruggie"
        ))
        self.play(Blink(randy))
        self.wait()

class ShowIndependenceSymbolically(Scene):
    def construct(self):
        filler_tex = "Filler"
        rhs = TexMobject("=", "0.8")
        rhs.set_color_by_tex("0.8", YELLOW)
        rhs.next_to(ORIGIN, RIGHT, LARGE_BUFF)
        lhs = TexMobject("P(", filler_tex, "|", filler_tex, ")")
        lhs.next_to(rhs, LEFT)
        VGroup(lhs, rhs).scale(1.5)
        for part in lhs.get_parts_by_tex(filler_tex):
            slot_group = get_slot_group(
                [True, True, True],
                buff = SMALL_BUFF,
                include_qs = False,
            )
            slot_group.replace(part, dim_to_match = 0)
            lhs.submobjects[lhs.index_of_part(part)] = slot_group
        VGroup(*lhs[1].content[:2]).set_fill(BLACK, 0)
        condition = lhs[3]
        condition.content[2].set_fill(BLACK, 0)
        bool_lists = [
            [False], [True, False], [False, True], [True],
        ]
        arrow = Arrow(UP, DOWN)
        arrow.next_to(condition, UP)
        arrow.set_color(RED)
        words = TextMobject("Doesn't matter")
        words.set_color(RED)
        words.next_to(arrow, UP)

        self.add(rhs, lhs, arrow, words)
        self.wait()
        for bool_list in bool_lists:
            slot_group = get_slot_group(bool_list, SMALL_BUFF, False)
            slot_group.replace(condition)
            slot_group.move_to(condition, DOWN)
            self.play(Transform(condition, slot_group))
            self.wait()
            
class ComputeProbabilityOfOneWrong(Scene):
    CONFIG = {
        "score" : 2,
        "final_result_rhs_tex" : [
            "3", "(0.8)", "^2", "(0.2)", "=", "0.384",
        ],
        "default_bool" : True,
        "default_p" : "0.8",
        "default_q" : "0.2",
    }
    def construct(self):
        self.show_all_three_patterns()
        self.show_final_result()

    def show_all_three_patterns(self):
        probabilities = VGroup()
        point_8s = VGroup()
        point_2s = VGroup()
        for i in reversed(list(range(3))):
            bool_list = [self.default_bool]*3
            bool_list[i] = not self.default_bool
            probs = ["(%s)"%self.default_p]*3
            probs[i] = "(%s)"%self.default_q
            lhs = get_probability_of_slot_group(bool_list)
            rhs = TexMobject("=", *probs)
            rhs.set_color_by_tex("0.8", GREEN)
            rhs.set_color_by_tex("0.2", RED)
            point_8s.add(*rhs.get_parts_by_tex("0.8"))
            point_2s.add(*rhs.get_parts_by_tex("0.2"))
            rhs.next_to(lhs, RIGHT)
            probabilities.add(VGroup(lhs, rhs))
        probabilities.arrange(DOWN, buff = LARGE_BUFF)
        probabilities.center()

        self.play(Write(probabilities[0]))
        self.wait(2)
        for i in range(2):
            self.play(ReplacementTransform(
                probabilities[i].copy(),
                probabilities[i+1]
            ))
        self.wait()
        for group in point_8s, point_2s:
            self.play(LaggedStartMap(
                Indicate, group,
                rate_func = there_and_back,
                lag_ratio = 0.7
            ))
            self.wait()

    def show_final_result(self):
        result = TexMobject(
            "P(", "\\text{Score} = %s"%self.score, ")", "=",
            *self.final_result_rhs_tex
        )
        result.set_color_by_tex_to_color_map({
            "0.8" : GREEN,
            "0.2" : RED,
            "Score" : YELLOW,
        })
        result[-1].set_color(YELLOW)
        result.set_color_by_tex("0.8", GREEN)
        result.set_color_by_tex("0.2", RED)
        result.to_edge(UP)

        self.play(Write(result))
        self.wait()

class ComputeProbabilityOfOneRight(ComputeProbabilityOfOneWrong):
    CONFIG = {
        "score" : 1,
        "final_result_rhs_tex" : [
            "3", "(0.8)", "(0.2)", "^2", "=", "0.096",
        ],
        "default_bool" : False,
        "default_p" : "0.2",
        "default_q" : "0.8",
    }

class ShowFullDistribution(Scene):
    def construct(self):
        self.add_scores_one_and_two()
        self.add_scores_zero_and_three()
        self.show_bar_chart()
        self.compare_to_binomial_pattern()
        self.show_alternate_values_of_p()

    def add_scores_one_and_two(self):
        scores = VGroup(
            TexMobject(
                "P(", "\\text{Score} = 0", ")",
                "=", "(0.2)", "^3",
                "=", "0.008",
            ),
            TexMobject(
                "P(", "\\text{Score} = 1", ")",
                "=", "3", "(0.8)", "(0.2)", "^2", 
                "=", "0.096",
            ),
            TexMobject(
                "P(", "\\text{Score} = 2", ")",
                "=", "3", "(0.8)", "^2", "(0.2)",
                "=", "0.384",
            ),
            TexMobject(
                "P(", "\\text{Score} = 3", ")",
                "=", "(0.8)", "^3",
                "=", "0.512",
            ),
        )
        scores.arrange(
            DOWN, 
            buff = MED_LARGE_BUFF,
            aligned_edge = LEFT
        )
        scores.shift(MED_LARGE_BUFF*UP)
        scores.to_edge(LEFT)
        for score in scores:
            score.set_color_by_tex_to_color_map({
                "0.8" : GREEN,
                "0.2" : RED,
            })
            score[-1].set_color(YELLOW)

        self.add(*scores[1:3])
        self.scores = scores

    def add_scores_zero_and_three(self):
        self.p_slot_groups = VGroup()

        self.wait()
        self.add_edge_score(0, UP, False)
        self.add_edge_score(3, DOWN, True)

    def add_edge_score(self, index, vect, q_bool):
        score = self.scores[index]
        prob = VGroup(*score[:3])
        brace = Brace(prob, vect)
        p_slot_group = get_probability_of_slot_group([q_bool]*3)
        p_slot_group.next_to(brace, vect)
        group = VGroup(*it.chain(p_slot_group, brace, score))

        self.play(LaggedStartMap(
            FadeIn, group,
            run_time = 2,
            lag_ratio = 0.7,
        ))
        self.wait(2)
        self.p_slot_groups.add(brace, p_slot_group)

    def show_bar_chart(self):
        p_terms = VGroup()
        to_fade = VGroup(self.p_slot_groups)
        value_mobs = VGroup()
        for score in self.scores:
            p_terms.add(VGroup(*score[:3]))
            to_fade.add(VGroup(*score[3:-1]))
            value_mobs.add(score[-1])
        dist = get_binomial_distribution(3, 0.8)
        values = list(map(dist, list(range(4))))
        chart = BarChart(
            values, bar_names = list(range(4)),
        )
        chart.shift(DOWN)

        new_p_terms = VGroup(*[
            TexMobject("P(", "S=%d"%k, ")")
            for k in range(4)
        ])
        for term, bar in zip(new_p_terms, chart.bars):
            term[1].set_color(YELLOW)
            term.set_width(1.5*bar.get_width())
            term.next_to(bar, UP)

        self.play(
            ReplacementTransform(
                value_mobs, chart.bars,
                lag_ratio = 0.5,
                run_time = 2
            )
        )
        self.play(
            LaggedStartMap(FadeIn, VGroup(*it.chain(*[
                submob 
                for submob in chart
                if submob is not chart.bars
            ]))),
            Transform(p_terms, new_p_terms),
            FadeOut(to_fade),
        )
        self.wait(2)

        chart.bar_top_labels = p_terms
        chart.add(p_terms)
        self.bar_chart = chart

    def compare_to_binomial_pattern(self):
        dist = get_binomial_distribution(3, 0.5)
        values = list(map(dist, list(range(4))))
        alt_chart = BarChart(values)
        alt_chart.move_to(self.bar_chart)
        bars = alt_chart.bars
        bars.set_fill(GREY, opacity = 0.5)
        vect = 4*UP
        bars.shift(vect)
        nums = VGroup(*list(map(TexMobject, list(map(str, [1, 3, 3, 1])))))
        for num, bar in zip(nums, bars):
            num.next_to(bar, UP)
        bars_copy = bars.copy()

        self.play(
            LaggedStartMap(FadeIn, bars),
            LaggedStartMap(FadeIn, nums),
        )
        self.wait(2)
        self.play(bars_copy.shift, -vect)
        self.play(ReplacementTransform(
            bars_copy, self.bar_chart.bars
        ))
        self.wait(2)
        self.play(
            VGroup(self.bar_chart, bars, nums).to_edge, LEFT
        )

        self.alt_bars = bars
        self.alt_bars_labels = nums

    def show_alternate_values_of_p(self):
        new_prob = TexMobject(
            "P(", "\\text{Correct}", ")", "=", "0.8"
        )
        new_prob.set_color_by_tex("Correct", GREEN)
        new_prob.shift(FRAME_X_RADIUS*RIGHT/2)
        new_prob.to_edge(UP)

        alt_ps = 0.5, 0.65, 0.25
        alt_rhss = VGroup()
        alt_charts = VGroup()
        for p in alt_ps:
            rhs = TexMobject(str(p))
            rhs.set_color(YELLOW)
            rhs.move_to(new_prob[-1])
            alt_rhss.add(rhs)

            dist = get_binomial_distribution(3, p)
            values = list(map(dist, list(range(4))))
            chart = self.bar_chart.copy()
            chart.change_bar_values(values)
            for label, bar in zip(chart.bar_top_labels, chart.bars):
                label.next_to(bar, UP)
            alt_charts.add(chart)

        self.play(FadeIn(new_prob))
        self.play(Transform(new_prob[-1], alt_rhss[0]))
        point_5_probs = self.show_point_5_probs(new_prob)
        self.wait()
        self.play(Transform(self.bar_chart, alt_charts[0]))
        self.wait()
        self.play(FadeOut(point_5_probs))
        for rhs, chart in list(zip(alt_rhss, alt_charts))[1:]:
            self.play(Transform(new_prob[-1], rhs))
            self.play(Transform(self.bar_chart, chart))
            self.wait(2)

    def show_point_5_probs(self, mob):
        probs = VGroup()
        last = mob
        for k in range(4):
            buff = MED_LARGE_BUFF
            for indices in it.combinations(list(range(3)), k):
                bool_list = np.array([False]*3)
                bool_list[list(indices)] = True
                prob = get_probability_of_slot_group(bool_list)
                rhs = TexMobject("= (0.5)^3")
                rhs.next_to(prob, RIGHT)
                prob.add(rhs)
                prob.scale(0.9)
                prob.next_to(last, DOWN, buff)
                probs.add(prob)
                last = prob
                buff = SMALL_BUFF

        self.play(LaggedStartMap(FadeIn, probs))
        self.wait()
        return probs

class ProbablyWrong(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Probably wrong!",
            run_time = 1,
        )
        self.change_student_modes(
            *["angry"]*3,
            run_time = 1
        )
        self.wait()

class ShowTrueDistribution(PiCreatureScene):
    def construct(self):
        self.force_skipping()

        self.remove(self.randy)
        self.add_title()
        self.show_distributions()
        self.show_emotion()
        self.imagine_score_0()

        self.revert_to_original_skipping_status()
        self.get_angry()

    def add_title(self):
        title = TexMobject("P(", "\\text{Correct}", ")", "=", "0.65")
        title.to_edge(UP)
        title.set_color_by_tex("Correct", GREEN)

        self.add(title)
        self.title = title

    def show_distributions(self):
        dist = get_binomial_distribution(3, 0.65)
        values = np.array(list(map(dist, list(range(4)))))
        alt_values = values + [0.2, 0, 0, 0.2]
        alt_values /= sum(alt_values)
        chart = BarChart(values, bar_names = list(range(4)))
        bars = chart.bars
        old_bars = bars.copy()
        arrows = VGroup()
        for bar, old_bar in zip(bars, old_bars):
            for mob, vect in (bar, RIGHT), (old_bar, LEFT):
                mob.generate_target()
                mob.target.do_about_point(
                    mob.get_corner(DOWN+vect),
                    mob.target.stretch, 0.5, 0
                )
            old_bar.target.set_color(average_color(RED_E, BLACK))
            old_bar.target.set_stroke(width = 0)
            arrow = Arrow(ORIGIN, UP, buff = 0, color = GREEN)
            arrow.move_to(bar.get_bottom())
            arrow.shift(3*UP)
            arrows.add(arrow)
        for arrow in arrows[1:3]:
            arrow.rotate_in_place(np.pi)
            arrow.set_color(RED)
        arrows.set_color_by_gradient(BLUE, YELLOW)

        self.add(chart)
        self.play(*list(map(MoveToTarget, it.chain(bars, old_bars))))
        self.play(
            chart.change_bar_values, alt_values,
            *list(map(ShowCreation, arrows))
        )
        self.wait(2)

        self.bar_chart = chart
        self.old_bars = old_bars

    def show_emotion(self):
        randy = self.randy

        self.play(FadeIn(randy))
        self.play(randy.change, "sad")
        self.play(Blink(randy))

    def imagine_score_0(self):
        prob_rect = SurroundingRectangle(self.title[-1])
        bar_rect = SurroundingRectangle(VGroup(
            self.bar_chart.bars[0], self.old_bars[0],
            self.bar_chart.bar_labels[0],
        ))

        self.play(ShowCreation(prob_rect))
        self.wait()
        self.play(ReplacementTransform(
            prob_rect, bar_rect
        ))
        self.wait()
        self.play(FadeOut(bar_rect))

    def get_angry(self):
        randy = self.randy

        self.play(randy.change, "angry")
        self.wait(2)
        self.play(PiCreatureSays(
            randy, "It's not representative!",
            target_mode = "pleading",
            bubble_kwargs = {"fill_opacity" : 1}
        ))
        self.wait(2)

    #####

    def create_pi_creature(self):
        self.randy = Randolph()
        self.randy.to_corner(DOWN+LEFT)
        return self.randy

class TeacherAssessingLiklihoodOfZero(TeacherStudentsScene):
    def construct(self):
        self.add_title()
        self.fade_other_students()
        self.show_independence_probability()
        self.teacher_reacts()

    def add_title(self):
        title = TexMobject("P(", "\\text{Correct}", ")", "=", "0.65")
        title.to_edge(UP)
        title.set_color_by_tex("Correct", GREEN)
        q_mark = TexMobject("?")
        q_mark.next_to(title[-2], UP, SMALL_BUFF)
        title.add(q_mark)

        self.add(title)
        self.title = title

    def fade_other_students(self):
        for student in self.students[0::2]:
            student.fade(0.7)
            self.pi_creatures.remove(student)

    def show_independence_probability(self):
        prob = get_probability_of_slot_group(3*[False])
        rhs = TexMobject("=", "(0.35)", "^3", "\\approx 4.3\\%")
        rhs.set_color_by_tex("0.35", RED)
        rhs.next_to(prob, RIGHT)
        prob.add(rhs)
        prob.next_to(self.teacher, UP+LEFT)
        words = TextMobject("Assuming independence")
        words.next_to(prob, UP)

        self.play(
            self.teacher.change, "raise_right_hand",
            FadeIn(words),
            Write(prob)
        )
        self.wait()

        self.ind_group = VGroup(prob, words)

    def teacher_reacts(self):
        ind_group = self.ind_group
        box = SurroundingRectangle(ind_group)
        box.set_stroke(WHITE, 0)
        ind_group.add(box)
        ind_group.generate_target()
        ind_group.target.scale(0.7)
        ind_group.target.to_corner(UP+RIGHT, MED_SMALL_BUFF)
        ind_group.target[-1].set_stroke(WHITE, 2)

        randy = self.students[1]

        self.teacher_says(
            "Highly unlikely",
            target_mode = "sassy",
            added_anims = [MoveToTarget(ind_group)],
            run_time = 2,
        )
        self.play(randy.change, "sad")
        self.wait(2)
        self.play(
            RemovePiCreatureBubble(
                self.teacher, target_mode = "guilty",
            ),
            PiCreatureSays(randy, "Wait!", target_mode = "surprised"),
            run_time = 1
        )
        self.wait(1)

class CorrelationsWith35Percent(ThousandPossibleQuizzes):
    def construct(self):
        self.add_top_calculation()
        self.show_first_split()
        self.show_second_split()
        self.show_third_split()
        self.comment_on_final_size()

    def add_top_calculation(self):
        equation = VGroup(
            get_probability_of_slot_group(3*[False]),
            TexMobject("="),
            get_probability_of_slot_group([False]),
            get_probability_of_slot_group(2*[False], [False]),
            get_probability_of_slot_group(3*[False], 2*[False]),
        )
        equation.arrange(RIGHT, buff = SMALL_BUFF)
        equation.to_edge(UP)

        self.add(equation)
        self.equation = equation

    def show_first_split(self):
        quizzes = self.get_thousand_quizzes()
        n = int(0.65*len(quizzes))
        top_part = VGroup(*quizzes[:n])
        bottom_part = VGroup(*quizzes[n:])
        parts = [top_part, bottom_part]
        for part, color in zip(parts, [GREEN, RED]):
            part.generate_target()
            for quiz in part.target:
                quiz[0].set_color(color)
        top_part.target.shift(UP)
        brace = Brace(bottom_part, LEFT)
        prop = TexMobject("0.35")
        prop.next_to(brace, LEFT)

        term = self.equation[2]
        term_brace = Brace(term, DOWN)

        self.add(quizzes)
        self.wait()
        self.play(
            GrowFromCenter(brace), 
            FadeIn(prop),
            *list(map(MoveToTarget, parts))
        )
        self.wait()
        self.play(
            top_part.fade, 0.8,
            Transform(brace, term_brace),
            prop.next_to, term_brace, DOWN,
        )
        self.wait()

        self.quizzes = bottom_part
        self.quizzes.sort(lambda p : p[0])

    def show_second_split(self):
        n = int(0.45*len(self.quizzes))
        left_part = VGroup(*self.quizzes[:n])
        right_part = VGroup(*self.quizzes[n:])
        parts = [left_part, right_part]
        for part, color in zip(parts, [GREEN, RED_E]):
            part.generate_target()
            for quiz in part.target:
                quiz[1].set_color(color)
        left_part.target.shift(LEFT)
        brace = Brace(right_part, UP)
        prop = TexMobject(">0.35")
        prop.next_to(brace, UP)

        term = self.equation[3]
        term_brace = Brace(term, DOWN)

        self.play(
            GrowFromCenter(brace),
            FadeIn(prop),
            *list(map(MoveToTarget, parts))
        )
        self.wait()
        self.play(
            Transform(brace, term_brace),
            prop.next_to, term_brace, DOWN
        )
        self.play(left_part.fade, 0.8)

        self.quizzes = right_part
        self.quizzes.sort(lambda p : -p[1])

    def show_third_split(self):
        quizzes = self.quizzes
        n = int(0.22*len(quizzes))
        top_part = VGroup(*quizzes[:n])
        bottom_part = VGroup(*quizzes[n:])
        parts = [top_part, bottom_part]
        for part, color in zip(parts, [GREEN, RED_B]):
            part.generate_target()
            for quiz in part.target:
                quiz[2].set_color(color)
        top_part.target.shift(0.5*UP)
        brace = Brace(bottom_part, LEFT)
        prop = TexMobject("\\gg 0.35")
        prop.next_to(brace, LEFT)

        term = self.equation[4]
        term_brace = Brace(term, DOWN)

        self.play(
            GrowFromCenter(brace), 
            FadeIn(prop),
            *list(map(MoveToTarget, parts))
        )
        self.wait()
        self.play(
            Transform(brace, term_brace),
            prop.next_to, term_brace, DOWN,
        )
        self.play(top_part.fade, 0.8)
        self.wait()

        self.quizzes = bottom_part

    def comment_on_final_size(self):
        rect = SurroundingRectangle(self.quizzes)
        words = TextMobject(
            "Much more than ", "$(0.35)^3 \\approx 4.3\\%$"
        )
        words.next_to(rect, LEFT)

        self.play(
            ShowCreation(rect),
            FadeIn(words)
        )
        self.wait()

class WeighingIndependenceAssumption(PiCreatureScene):
    def construct(self):
        randy = self.randy

        title = TextMobject("Independence")
        title.scale(1.5)
        title.to_edge(UP)
        self.add(title)
        formula = TexMobject(
            "P(", "A", "B", ")", "="
            "P(", "A", ")", "P(", "B", ")"
        )
        formula.set_color_by_tex("A", BLUE)
        formula.set_color_by_tex("B", GREEN)

        clean = TextMobject("Clean")
        clean.next_to(formula, UP)
        VGroup(clean, formula).next_to(randy, UP+LEFT)
        clean.save_state()
        clean.shift(2*(DOWN+RIGHT))
        clean.set_fill(opacity = 0)

        self.play(
            randy.change, "raise_left_hand", clean,
            clean.restore
        )
        self.play(Write(formula))
        self.play(
            randy.change, "raise_right_hand",
            randy.look, UP+RIGHT,
        )
        self.wait(2)

    ####

    def create_pi_creature(self):
        self.randy = Randolph().to_edge(DOWN)
        return self.randy

class NameBinomial(Scene):
    CONFIG = {
        "flip_indices" : [0, 2, 4, 5, 6, 7],
    }
    def construct(self):
        self.name_distribution()
        self.add_quiz_questions()
        self.change_to_gender()
        self.change_bar_chart_for_gender_example()
        self.point_out_example_input()
        self.write_probability_of_girl()
        self.think_through_probabilities()

    def name_distribution(self):
        ns = [3, 10]
        p = 0.65
        charts = VGroup()
        for n in ns:
            dist = get_binomial_distribution(n, p)
            values = list(map(dist, list(range(n+1))))
            chart = BarChart(values, bar_names = list(range(n+1)))
            chart.to_edge(LEFT)
            charts.add(chart)

        probability = TexMobject(
            "P(", "\\checkmark", ")", "=", str(p)
        )
        probability.set_color_by_tex("checkmark", GREEN)
        probability.move_to(charts, UP)

        title = TextMobject("``Binomial distribution''")
        title.next_to(charts, UP)
        title.to_edge(UP)
        formula = TexMobject(
            "P(X=", "k", ")=", 
            "{n \\choose k}", 
            "p", "^k",
            "(1-", "p", ")", "^{n-", "k}",
            arg_separator = ""
        )
        formula.set_color_by_tex("p", YELLOW)
        formula.set_color_by_tex("k", GREEN)
        choose_part = formula.get_part_by_tex("choose")
        choose_part.set_color(WHITE)
        choose_part[-2].set_color(GREEN)
        formula.to_corner(UP+RIGHT)

        self.add(charts[0], probability)
        self.wait()
        self.play(Write(title))
        self.wait()
        self.play(ReplacementTransform(*charts))
        self.play(Write(formula))
        self.wait()
        self.play(
            formula.scale, 0.7,
            formula.next_to, charts, DOWN,
        )

        self.chart = charts[1]
        self.probability = probability
        self.title = title
        self.formula = formula

    def add_quiz_questions(self):
        n = 10
        checkmarks = VGroup(*[
            TexMobject("\\checkmark").set_color(GREEN)
            for x in range(n)
        ])
        checkmarks.arrange(DOWN, buff = 0.3)
        crosses = VGroup()
        arrows = VGroup()
        for checkmark in checkmarks:
            cross = TexMobject("\\times")
            cross.set_color(RED)
            cross.next_to(checkmark, RIGHT, LARGE_BUFF)
            crosses.add(cross)
            arrow = Arrow(
                checkmark, cross,
                tip_length = 0.15,
                color = WHITE
            )
            arrows.add(arrow)
        full_group = VGroup(checkmarks, crosses, arrows)
        full_group.center().to_corner(UP + RIGHT, buff = MED_LARGE_BUFF)
        flip_indices = self.flip_indices
        flipped_arrows, faded_crosses, full_checks = [
            VGroup(*[group[i] for i in flip_indices])
            for group in (arrows, crosses, checkmarks)
        ]
        faded_checkmarks = VGroup(*[m for m in checkmarks if m not in full_checks])

        self.play(*[
            LaggedStartMap(
                Write, mob,
                run_time = 3,
                lag_ratio = 0.3
            )
            for mob in full_group
        ])
        self.wait()
        self.play(
            LaggedStartMap(
                Rotate, flipped_arrows,
                angle = np.pi,
                in_place = True,
                run_time = 2,
                lag_ratio = 0.5
            ),
            faded_crosses.set_fill, None, 0.5,
            faded_checkmarks.set_fill, None, 0.5,
        )
        self.wait()

        self.checkmarks = checkmarks
        self.crosses = crosses
        self.arrows = arrows

    def change_to_gender(self):
        flip_indices = self.flip_indices
        male = self.get_male()
        female = self.get_female()

        girls, boys = [
            VGroup(*[
                template.copy().move_to(mob)
                for mob in group
            ])
            for template, group in [
                (female, self.checkmarks), (male, self.crosses)
            ]
        ]
        for i in range(len(boys)):
            mob = boys[i] if i in flip_indices else girls[i]
            mob.set_fill(opacity = 0.5)

        brace = Brace(girls, LEFT)
        words = brace.get_text("$n$ children")

        self.play(
            GrowFromCenter(brace),
            FadeIn(words)
        )
        for m1, m2 in (self.crosses, boys), (self.checkmarks, girls):
            self.play(ReplacementTransform(
                m1, m2,
                lag_ratio = 0.5,
                run_time = 3
            ))
        self.wait()

        self.boys = boys
        self.girls = girls
        self.children_brace = brace
        self.n_children_words = words

    def change_bar_chart_for_gender_example(self):
        checkmark = self.probability.get_part_by_tex("checkmark")
        p_mob = self.probability[-1]

        female = self.get_female()
        female.move_to(checkmark)
        new_p_mob = TexMobject("0.49")
        new_p_mob.move_to(p_mob, LEFT)

        dist = get_binomial_distribution(10, 0.49)
        values = list(map(dist, list(range(11))))

        self.play(
            Transform(checkmark, female),
            Transform(p_mob, new_p_mob),
        )
        self.play(self.chart.change_bar_values, values)
        self.wait()

    def point_out_example_input(self):
        boy_girl_groups = VGroup(*[
            VGroup(boy, girl)
            for boy, girl in zip(self.boys, self.girls)
        ])
        girl_rects = VGroup(*[
            SurroundingRectangle(
                boy_girl_groups[i], 
                color = MAROON_B,
                buff = SMALL_BUFF,
            )
            for i in sorted(self.flip_indices)
        ])

        chart = self.chart
        n_girls = len(girl_rects)
        chart_rect = SurroundingRectangle(
            VGroup(chart.bars[n_girls], chart.bar_labels[n_girls]),
            buff = SMALL_BUFF
        )

        prob = TexMobject(
            "P(", "\\# \\text{Girls}", "=", "6", ")"
        )
        prob.set_color_by_tex("Girls", MAROON_B)
        arrow = Arrow(UP, ORIGIN, tip_length = 0.15)
        arrow.set_color(MAROON_B)
        arrow.next_to(prob, DOWN)
        prob.add(arrow)
        prob.next_to(chart_rect, UP)
        girls = VGroup(*[self.girls[i] for i in self.flip_indices])


        self.play(ShowCreation(chart_rect))
        self.play(LaggedStartMap(
            ShowCreation, girl_rects,
            run_time = 2,
            lag_ratio = 0.5,
        ))
        self.wait()

        self.play(Write(prob))
        self.play(LaggedStartMap(
            Indicate, girls,
            run_time = 3,
            lag_ratio = 0.3,
            rate_func = there_and_back
        ))
        self.play(FadeOut(prob))
        self.wait()

        self.chart_rect = chart_rect
        self.girl_rects = girl_rects

    def write_probability_of_girl(self):
        probability = self.probability
        probability_copies = VGroup(*[
            probability.copy().scale(0.7).next_to(
                girl, LEFT, MED_LARGE_BUFF
            )
            for girl in self.girls
        ])

        self.play(FocusOn(probability))
        self.play(Indicate(probability[-1]))
        self.wait()
        self.play(
            ReplacementTransform(
                VGroup(probability.copy()), probability_copies
            ),
            FadeOut(self.children_brace),
            FadeOut(self.n_children_words),
        )
        self.wait()

        self.probability_copies = probability_copies

    def think_through_probabilities(self):
        randy = Randolph().scale(0.5)
        randy.next_to(self.probability_copies, LEFT, LARGE_BUFF)

        self.play(FadeIn(randy))
        self.play(randy.change, "pondering")
        self.play(Blink(randy))
        self.wait()

    ##

    def get_male(self):
        return TexMobject("\\male").scale(1.3).set_color(BLUE)

    def get_female(self):
        return TexMobject("\\female").scale(1.3).set_color(MAROON_B)

class CycleThroughPatterns(NameBinomial):
    CONFIG = {
        "n_patterns_shown" : 100,
        "pattern_scale_value" : 2.7,
        "n" : 10,
        "k" : 6,
    }
    def construct(self):
        n = self.n
        k = self.k
        question = TextMobject(
            "How many patterns have \\\\ %d "%k, 
            "$\\female$",
            " and %d "%(n-k),
            "$\\male$",
            "?",
            arg_separator = ""
        )
        question.set_color_by_tex("male", BLUE)
        question.set_color_by_tex("female", MAROON_B)
        question.set_width(FRAME_WIDTH - 1)
        question.to_edge(UP, buff = LARGE_BUFF)
        self.add(question)

        all_combinations = list(it.combinations(list(range(n)), k))
        shown_combinations = all_combinations[:self.n_patterns_shown]
        patterns = VGroup(*[
            self.get_pattern(indicies)
            for indicies in shown_combinations
        ])
        patterns.to_edge(DOWN, buff = LARGE_BUFF)
        pattern = patterns[0]
        self.add(pattern)
        for new_pattern in ProgressDisplay(patterns[1:]):
            self.play(*[
                Transform(
                    getattr(pattern, attr),
                    getattr(new_pattern, attr),
                    path_arc = np.pi
                )
                for attr in ("boys", "girls")
            ])

    ####

    def get_pattern(self, indices):
        pattern = VGroup()
        pattern.boys = VGroup()
        pattern.girls = VGroup()
        for i in range(self.n):
            if i in indices:
                mob = self.get_female()  
                pattern.girls.add(mob)
            else:
                mob = self.get_male()
                pattern.boys.add(mob)
            mob.shift(i*MED_LARGE_BUFF*RIGHT)
            pattern.add(mob)
        pattern.scale(self.pattern_scale_value)
        pattern.to_edge(LEFT)
        return pattern

class Compute6of10GirlsProbability(CycleThroughPatterns):
    def construct(self):
        self.show_combinations()
        self.write_n_choose_k()

    def show_combinations(self):
        pattern_rect = ScreenRectangle(height = 4)
        pattern_rect.center()
        pattern_rect.to_edge(UP, buff = MED_SMALL_BUFF)

        self.add(pattern_rect)
        self.wait(5)

        self.pattern_rect = pattern_rect

    def write_n_choose_k(self):
        brace = Brace(self.pattern_rect, DOWN)
        ten_choose_six = brace.get_tex("{10 \\choose 6}")
        see_chapter_one = TextMobject("(See chapter 1)")
        see_chapter_one.next_to(ten_choose_six, DOWN)
        see_chapter_one.set_color(GREEN)
        computation = TexMobject(
            "=\\frac{%s}{%s}"%(
                 "\\cdot ".join(map(str, list(range(10, 4, -1)))),
                 "\\cdot ".join(map(str, list(range(1, 7)))),
            )
        )
        computation.move_to(ten_choose_six, UP)
        rhs = TexMobject("=", "210")
        rhs.next_to(computation, RIGHT)

        self.play(
            FadeIn(see_chapter_one),
            GrowFromCenter(brace)
        )
        self.play(Write(ten_choose_six))
        self.wait(2)
        self.play(
            ten_choose_six.next_to, computation.copy(), LEFT,
            Write(VGroup(computation, rhs))
        )
        self.wait()

        self.ten_choose_six = ten_choose_six
        self.rhs = rhs

class ProbabilityOfAGivenBoyGirlPattern(CycleThroughPatterns):
    def construct(self):
        self.write_total_count()
        self.write_example_probability()
        self.write_total_probability()

    def write_total_count(self):
        count = TextMobject(
            "${10 \\choose 6}$", " $= 210$",
            "total patterns."
        )
        count.to_edge(UP)
        self.add(count)

        self.count = count

    def write_example_probability(self):
        prob = TexMobject("P\\big(", "O "*15, "\\big)", "=")
        indices = [1, 2, 4, 6, 8, 9]
        pattern = self.get_pattern(indices)
        pattern.replace(prob[1], dim_to_match = 0)
        prob.submobjects[1] = pattern
        prob.next_to(self.count, DOWN, LARGE_BUFF)

        gp = TexMobject("P(\\female)")
        gp[2].set_color(MAROON_B)
        bp = TexMobject("P(\\male)")
        bp[2].set_color(BLUE)
        gp_num = TexMobject("(0.49)").set_color(MAROON_B)
        bp_num = TexMobject("(0.51)").set_color(BLUE)
        gp_nums = VGroup()
        bp_nums = VGroup()
        factored = VGroup()
        factored_in_nums = VGroup()
        for i in range(10):
            if i in indices:
                num_mob = gp_num.copy()
                gp_nums.add(num_mob)
                p_mob = gp.copy()
            else:
                num_mob = bp_num.copy()
                bp_nums.add(num_mob)
                p_mob = bp.copy()
            factored_in_nums.add(num_mob)
            factored.add(p_mob)
        for group in factored, factored_in_nums:
            group.arrange(RIGHT, buff = SMALL_BUFF)
            group.next_to(prob, DOWN, MED_LARGE_BUFF)
        gp_nums.save_state()
        bp_nums.save_state()

        final_probability = TexMobject(
            "(0.49)^6", "(0.51)^4"
        )
        final_probability.set_color_by_tex("0.49", MAROON_B)
        final_probability.set_color_by_tex("0.51", BLUE)
        final_probability.next_to(factored_in_nums, DOWN, LARGE_BUFF)

        self.play(FadeIn(prob))
        self.wait()
        self.play(ReplacementTransform(
            pattern.copy(), factored,
            run_time = 1.5,
        ))
        self.wait(2)
        self.play(ReplacementTransform(
            factored, factored_in_nums,
            run_time = 2,
            lag_ratio = 0.5
        ))
        self.wait(2)
        for group, tex in (gp_nums, "0.49"), (bp_nums, "0.51"):
            part = final_probability.get_part_by_tex(tex)
            self.play(group.shift, MED_LARGE_BUFF*DOWN)
            self.play(
                ReplacementTransform(
                    group.copy(), VGroup(VGroup(*part[:-1]))
                ),
                Write(part[-1])
            )
            self.wait()
            self.play(group.restore)
        self.wait()

        self.final_probability = final_probability

    def write_total_probability(self):
        ten_choose_six = self.count[0].copy()
        ten_choose_six.generate_target()
        ten_choose_six.target.move_to(self.final_probability)
        p_tex = TexMobject("P(", "\\text{6 Girls}", ")", "=")
        p_tex.set_color_by_tex("Girls", MAROON_B)
        p_tex.next_to(ten_choose_six.target, LEFT)

        self.play(
            Write(p_tex, run_time = 2),
            self.final_probability.next_to, 
            ten_choose_six.target, RIGHT
        )
        self.play(MoveToTarget(ten_choose_six))
        self.wait()

class CycleThroughPatternsForThree(CycleThroughPatterns):
    CONFIG = {
        "k" : 3,
        "n_patterns_shown" : 20,
    }

class GeneralBinomialDistributionValues(Scene):
    CONFIG = {
        "n" : 10,
        "alt_n" : 8,
        "p" : 0.49,
    }
    def construct(self):
        self.add_chart()
        self.show_a_few_values()
        self.compare_to_pascal_row()
        self.mention_center_concentration()
        self.generalize()
        self.play_with_p_value()

    def add_chart(self):
        dist = get_binomial_distribution(self.n, self.p)
        values = list(map(dist, list(range(self.n+1))))
        chart = BarChart(
            values,
            bar_names = list(range(self.n+1))
        )
        chart.to_edge(LEFT)

        full_probability = self.get_probability_expression(
            "10", "k", "(0.49)", "(0.51)"
        )
        full_probability.next_to(chart, UP, aligned_edge = LEFT)

        self.add(chart)

        self.chart = chart
        self.full_probability = full_probability

    def show_a_few_values(self):
        chart = self.chart
        probabilities = VGroup()
        for i, bar in enumerate(chart.bars):
            prob = self.get_probability_expression(
                "10", str(i), "(0.49)", "(0.51)",
                full = False
            )
            arrow = Arrow(
                UP, DOWN, 
                color = WHITE,
                tip_length = 0.15
            )
            arrow.next_to(bar, UP, SMALL_BUFF)
            prob.next_to(arrow, UP, SMALL_BUFF)
            ##
            prob.shift(LEFT)
            prob.shift_onto_screen()
            prob.shift(RIGHT)
            ##
            prob.add(arrow)            
            probabilities.add(prob)
        shown_prob = probabilities[6].copy()

        self.play(FadeIn(shown_prob))
        self.wait()
        self.play(LaggedStartMap(
            FadeIn, self.full_probability,
            run_time = 4,
            lag_ratio = 0.5,
        ))
        self.wait()
        last_k = 6
        for k in 3, 8, 5, 9, 6:
            self.play(Transform(
                shown_prob, probabilities[k],
                path_arc = -np.pi/6 if k > last_k else np.pi/6
            ))
            self.wait(2)
            last_k = k

        self.shown_prob = shown_prob

    def compare_to_pascal_row(self):
        triangle = PascalsTriangle(nrows = 11)
        triangle.set_width(6)
        triangle.to_corner(UP+RIGHT)
        last_row = VGroup(*[
            triangle.coords_to_mobs[10][k]
            for k in range(11)
        ])
        ten_choose_ks = VGroup()
        for k, mob in enumerate(last_row):
            ten_choose_k = TexMobject("10 \\choose %s"%k)
            ten_choose_k.scale(0.5)
            ten_choose_k.stretch(0.8, 0)
            ten_choose_k.next_to(mob, DOWN)
            ten_choose_ks.add(ten_choose_k)
        ten_choose_ks.set_color_by_gradient(BLUE, YELLOW)

        self.play(
            LaggedStartMap(FadeIn, triangle),
            FadeOut(self.shown_prob)
        )
        self.play(
            last_row.set_color_by_gradient, BLUE, YELLOW,
            Write(ten_choose_ks, run_time = 2)
        )
        self.wait()
        self.play(ApplyWave(self.chart.bars, direction = UP))
        self.play(FocusOn(last_row))
        self.play(LaggedStartMap(
            ApplyMethod, last_row,
            lambda m : (m.scale_in_place, 1.2),
            rate_func = there_and_back,
        ))
        self.wait()

        self.pascals_triangle = triangle
        self.ten_choose_ks = ten_choose_ks

    def mention_center_concentration(self):
        bars = self.chart.bars
        bars.generate_target()
        bars.save_state()
        bars.target.arrange(UP, buff = 0)
        bars.target.stretch_to_fit_height(self.chart.height)
        bars.target.move_to(
            self.chart.x_axis.point_from_proportion(0.05),
            DOWN
        )
        brace = Brace(VGroup(*bars.target[4:7]), RIGHT)
        words = brace.get_text("Most probability \\\\ in middle values")

        self.play(MoveToTarget(bars))
        self.play(
            GrowFromCenter(brace),
            FadeIn(words)
        )
        self.wait(2)
        self.play(
            bars.restore,
            *list(map(FadeOut, [
                brace, words, 
                self.pascals_triangle,
                self.ten_choose_ks
            ]))
        )

    def generalize(self):
        alt_n = self.alt_n
        dist = get_binomial_distribution(alt_n, self.p)
        values = list(map(dist, list(range(alt_n + 1))))
        alt_chart = BarChart(
            values, bar_names = list(range(alt_n + 1))
        )
        alt_chart.move_to(self.chart)

        alt_probs = [
            self.get_probability_expression("n", "k", "(0.49)", "(0.51)"),
            self.get_probability_expression("n", "k", "p", "(1-p)"),
        ]
        for prob in alt_probs:
            prob.move_to(self.full_probability)

        self.play(FocusOn(
            self.full_probability.get_part_by_tex("choose")
        ))
        self.play(
            ReplacementTransform(self.chart, alt_chart),
            Transform(self.full_probability, alt_probs[0])
        )
        self.chart = alt_chart
        self.wait(2)
        self.play(Transform(self.full_probability, alt_probs[1]))
        self.wait()

    def play_with_p_value(self):
        p = self.p
        interval = UnitInterval(color = WHITE)
        interval.set_width(5)
        interval.next_to(self.full_probability, DOWN, LARGE_BUFF)
        interval.add_numbers(0, 0.5, 1)
        triangle = RegularPolygon(
            n=3, start_angle = -np.pi/2,
            fill_color = MAROON_B,
            fill_opacity = 1,
            stroke_width = 0,
        )
        triangle.set_height(0.25)
        triangle.move_to(interval.number_to_point(p), DOWN)
        p_mob = TexMobject("p")
        p_mob.set_color(MAROON_B)
        p_mob.next_to(triangle, UP, SMALL_BUFF)
        triangle.add(p_mob)

        new_p_values = [0.8, 0.4, 0.2, 0.9, 0.97, 0.6]

        self.play(
            ShowCreation(interval),
            Write(triangle, run_time = 1)
        )
        self.wait()
        for new_p in new_p_values:
            p = new_p
            dist = get_binomial_distribution(self.alt_n, p)
            values = list(map(dist, list(range(self.alt_n + 1))))
            self.play(
                self.chart.change_bar_values, values,
                triangle.move_to, interval.number_to_point(p), DOWN
            )
            self.wait()

    #######

    def get_probability_expression(
        self, n = "n", k = "k", p = "p", q = "(1-p)",
        full = True
        ):
        args = []
        if full:
            args += ["P(", "\\# \\text{Girls}", "=", k, ")", "="]
        args += [
            "{%s \\choose %s}"%(n, k),
            p, "^%s"%k,
            q, "^{%s"%n, "-", "%s}"%k,
        ]
        result = TexMobject(*args, arg_separator = "")
        color_map = {
            "Girls" : MAROON_B,
            n : WHITE,
            k : YELLOW,
            p : MAROON_B,
            q : BLUE,
        }
        result.set_color_by_tex_to_color_map(color_map)
        choose_part = result.get_part_by_tex("choose")
        choose_part.set_color(WHITE)
        VGroup(*choose_part[1:1+len(n)]).set_color(color_map[n])
        VGroup(*choose_part[-1-len(k):-1]).set_color(color_map[k])
        return result

class PointOutSimplicityOfFormula(TeacherStudentsScene, GeneralBinomialDistributionValues):
    def construct(self):
        prob = self.get_probability_expression(full = False)
        corner = self.teacher.get_corner(UP+LEFT)
        prob.next_to(corner, UP, MED_LARGE_BUFF)
        prob.save_state()
        prob.move_to(corner)
        prob.set_fill(opacity = 0)

        self.play(
            prob.restore,
            self.teacher.change_mode, "raise_right_hand"
        )
        self.change_student_modes(
            *["pondering"]*3,
            look_at_arg = prob
        )
        self.wait()
        self.student_says(
            "Simpler than I feared",
            target_mode = "hooray",
            student_index = 0,
            added_anims = [prob.to_corner, UP+RIGHT]
        )
        self.wait()
        self.teacher_says("Due to \\\\ independence")
        self.wait(2)

class CorrectForDependence(NameBinomial):
    CONFIG = {
        "flip_indices" : [3, 6, 8],
    }
    def setup(self):
        self.force_skipping()
        self.name_distribution()
        self.add_quiz_questions()
        self.revert_to_original_skipping_status()

    def construct(self):
        self.mention_dependence()
        self.show_tendency_to_align()
        self.adjust_chart()

    def mention_dependence(self):
        brace = Brace(self.checkmarks, LEFT)
        words = brace.get_text("What if there's \\\\ correlation?")
        formula = self.formula
        cross = Cross(formula)

        self.play(
            GrowFromCenter(brace),
            Write(words)
        )
        self.wait()
        self.play(ShowCreation(cross))
        self.wait()

    def show_tendency_to_align(self):
        checkmarks = self.checkmarks
        arrows = self.arrows
        crosses = self.crosses
        groups = [
            VGroup(*trip)
            for trip in zip(checkmarks, arrows, crosses)
        ]
        top_rect = SurroundingRectangle(groups[0])
        top_rect.set_color(GREEN)
        indices_to_follow = [1, 4, 5, 7]

        self.play(ShowCreation(top_rect))
        self.play(*self.get_arrow_flip_anims([0]))
        self.wait()
        self.play(*self.get_arrow_flip_anims(indices_to_follow))
        self.play(FocusOn(self.chart.bars))

    def adjust_chart(self):
        chart = self.chart
        bars = chart.bars
        old_bars = bars.copy()
        old_bars.generate_target()
        bars.generate_target()
        for group, vect in (old_bars, LEFT), (bars, RIGHT):
            for bar in group.target:
                side = bar.get_edge_center(vect)
                bar.stretch(0.5, 0)
                bar.move_to(side, vect)
        for bar in old_bars.target:
            bar.set_color(average_color(RED_E, BLACK))

        dist = get_binomial_distribution(10, 0.65)
        values = np.array(list(map(dist, list(range(11)))))
        alt_values = values + 0.1
        alt_values[0] -= 0.06
        alt_values[1] -= 0.03
        alt_values /= sum(alt_values)
        arrows = VGroup()
        arrow_template = Arrow(
            0.5*UP, ORIGIN, buff = 0, 
            tip_length = 0.15,
            color = WHITE
        )
        for value, alt_value, bar in zip(values, alt_values, bars):
            arrow = arrow_template.copy()
            if value < alt_value:
                arrow.rotate(np.pi, about_point = ORIGIN)
            arrow.next_to(bar, UP)
            arrows.add(arrow)

        self.play(
            MoveToTarget(old_bars),
            MoveToTarget(bars),
        )
        self.wait()
        self.play(*list(map(ShowCreation, arrows)))
        self.play(chart.change_bar_values, alt_values)

    ######

    def get_arrow_flip_anims(self, indices):
        checkmarks, arrows, crosses = movers = [
            VGroup(*[
                group[i]
                for i in range(len(group))
                if i in indices
            ])
            for group in (self.checkmarks, self.arrows, self.crosses)
        ]
        for arrow in arrows:
            arrow.target = arrow.deepcopy()
            arrow.target.rotate_in_place(np.pi)
        for group in checkmarks, crosses:
            for mob, arrow in zip(group, arrows):
                mob.generate_target()
                c = mob.get_center()
                start, end = arrow.target.get_start_and_end()
                to_end = get_norm(c - end)
                to_start = get_norm(c - start)
                if to_end < to_start:
                    mob.target.set_fill(opacity = 1)
                else:
                    mob.target.set_fill(opacity = 0.5)
        for checkmark in checkmarks:
            checkmark.target.scale_in_place(1.2)

        kwargs = {"path_arc" : np.pi}
        if len(indices) > 1:
            kwargs.update({"run_time" : 2})
        return [
            LaggedStartMap(
                MoveToTarget, mover,
                **kwargs
            )
            for mover in movers
        ]

class ButWhatsTheAnswer(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "But what's the \\\\ actual answer?",
            target_mode = "confused"
        )
        self.change_student_modes(*["confused"]*3)
        self.wait()
        self.play(self.teacher.change, "pondering")
        self.wait(3)

class PermuteQuizQuestions(Scene):
    def construct(self):
        quiz = get_quiz(
            "Define ``Brachistochrone''",
            "Define ``Tautochrone''",
            "Define ``Cycloid''",
        )
        questions = [
            VGroup(*q[2:])
            for q in quiz.questions
        ]
        colors = [BLUE, GREEN, RED]
        for color, question in zip(colors, questions):
            question.set_color(color)
        quiz.scale(2)

        self.add(quiz)
        self.wait()
        for m1, m2 in it.combinations(questions, 2):
            self.play(
                m1.move_to, m2, LEFT,
                m2.move_to, m1, LEFT,
                path_arc = np.pi
            )
            self.wait()

class AssumeOrderDoesntMatter(Scene):
    def construct(self):
        self.force_skipping()

        self.add_title()
        self.show_equality()
        self.mention_correlation()

        self.revert_to_original_skipping_status()
        self.coming_soon()

    def add_title(self):
        title = TextMobject(
            "Softer simplifying assumption: " +\
            "Order doesn't matter"
        )
        title.to_edge(UP)

        self.add(title)
        self.title = title

    def show_equality(self):
        n = 3
        prob_groups = VGroup(*[
            VGroup(*list(map(
                get_probability_of_slot_group,
                [t for t in it.product(*[[True, False]]*n) if sum(t) == k]
            )))
            for k in range(n+1)
        ])
        for prob_group in prob_groups:
            for prob in prob_group[:-1]:
                equals = TexMobject("=")
                equals.next_to(prob, RIGHT)
                prob.add(equals)
            prob_group.arrange(RIGHT)
            max_width = FRAME_WIDTH - 1
            if prob_group.get_width() > max_width:
                prob_group.set_width(max_width)
        prob_groups.arrange(DOWN, buff = 0.7)
        prob_groups.next_to(self.title, DOWN, MED_LARGE_BUFF)

        self.play(FadeIn(
            prob_groups[1],
            run_time = 2,
            lag_ratio = 0.5
        ))
        self.wait(2)
        self.play(FadeIn(
            VGroup(prob_groups[0], *prob_groups[2:]),
            run_time = 3,
            lag_ratio = 0.5
        ))
        self.wait()

        self.prob_groups = prob_groups

    def mention_correlation(self):
        assumption_group = VGroup(*self.get_top_level_mobjects())
        question = TextMobject(
            "But what is ", "``correlation''", "?",
            arg_separator = ""
        )
        question.set_color(BLUE)
        question.to_edge(UP)
        bottom = question.get_bottom()

        self.play(
            Write(question),
            assumption_group.next_to, bottom, DOWN, LARGE_BUFF
        )
        self.wait()

        self.assumption_group = assumption_group
        self.question = question

    def coming_soon(self):
        self.play(
            LaggedStartMap(
                ApplyMethod, self.assumption_group,
                lambda m : (m.shift, FRAME_HEIGHT*DOWN),
                remover = True,
            ),
            ApplyMethod(
                self.question.center,
                rate_func = squish_rate_func(smooth, 0.5, 1),
                run_time = 2
            )
        )

        part = self.question.get_part_by_tex("correlation")
        brace = Brace(part, UP)
        words = brace.get_text("Coming soon!")
        self.play(
            GrowFromCenter(brace),
            part.set_color, YELLOW
        )
        self.play(Write(words))
        self.wait()



class FormulaCanBeRediscovered(PointOutSimplicityOfFormula):
    def construct(self):
        prob = self.get_probability_expression(full = False)
        corner = self.teacher.get_corner(UP+LEFT)
        prob.next_to(corner, UP, MED_LARGE_BUFF)
        brace = Brace(prob, UP)
        rediscover = brace.get_text("Rediscover")

        self.play(
            Write(prob),
            self.teacher.change, "hesitant", prob
        )
        self.wait()
        self.play(
            GrowFromCenter(brace),
            Write(rediscover, run_time = 1)
        )
        self.change_student_modes(*["happy"]*3)
        self.wait(2)

class CompareTwoSituations(PiCreatureScene):
    def construct(self):
        randy = self.randy
        top_left, top_right = screens = [
            ScreenRectangle(height = 3).to_corner(vect)
            for vect in (UP+LEFT, UP+RIGHT)
        ]
        arrow = DoubleArrow(*screens, buff = SMALL_BUFF)
        arrow.set_color(BLUE)

        for screen, s in zip(screens, ["left", "right"]):
            self.play(
                randy.change, "raise_%s_hand"%s, screen,
                ShowCreation(screen)
            )
            self.wait(3)
        self.play(
            randy.change, "pondering", arrow,
            ShowCreation(arrow)
        )
        self.wait(2)

    ####

    def create_pi_creature(self):
        self.randy = Randolph().to_edge(DOWN)
        return self.randy

class SkepticalOfDistributions(TeacherStudentsScene):
    CONFIG = {
        "chart_height" : 3,
    }
    def construct(self):
        self.show_binomial()
        self.show_alternate_distributions()
        self.emphasize_underweighted_tails()

    def show_binomial(self):
        binomial = self.get_binomial()
        binomial.next_to(self.teacher.get_corner(UP+LEFT), UP)
        title = TextMobject("Probable scores")
        title.scale(0.85)
        title.next_to(binomial.bars, UP, 1.5*LARGE_BUFF)

        self.play(
            Write(title, run_time = 1),
            FadeIn(binomial, run_time = 1, lag_ratio = 0.5),
            self.teacher.change, "raise_right_hand"
        )
        for values in binomial.values_list:
            self.play(binomial.change_bar_values, values)
            self.wait()
        self.student_says(
            "Is that valid?", target_mode = "sassy",
            student_index = 0,
            run_time = 1
        )
        self.play(self.teacher.change, "guilty")
        self.wait()

        binomial.add(title)
        self.binomial = binomial

    def show_alternate_distributions(self):
        poisson = self.get_poisson()
        VGroup(poisson, poisson.title).next_to(
            self.students, UP, LARGE_BUFF
        ).shift(RIGHT)
        gaussian = self.get_gaussian()
        VGroup(gaussian, gaussian.title).next_to(
            poisson, RIGHT, LARGE_BUFF
        )


        self.play(
            FadeIn(poisson, lag_ratio = 0.5),
            RemovePiCreatureBubble(self.students[0]),
            self.teacher.change, "raise_right_hand",
            self.binomial.scale, 0.5,
            self.binomial.to_corner, UP+LEFT,
        )
        self.play(Write(poisson.title, run_time = 1))
        self.play(FadeIn(gaussian, lag_ratio = 0.5))
        self.play(Write(gaussian.title, run_time = 1))
        self.wait(2)
        self.change_student_modes(
            *["sassy"]*3,
            added_anims = [self.teacher.change, "plain"]
        )
        self.wait(2)

        self.poisson = poisson
        self.gaussian = gaussian

    def emphasize_underweighted_tails(self):
        poisson_arrows = VGroup()
        arrow_template = Arrow(
            ORIGIN, UP, color = GREEN,
            tip_length = 0.15
        )
        for bar in self.poisson.bars[-4:]:
            arrow = arrow_template.copy()
            arrow.next_to(bar, UP, SMALL_BUFF)
            poisson_arrows.add(arrow)

        gaussian_arrows = VGroup()
        for prop in 0.2, 0.8:
            point = self.gaussian[0][0].point_from_proportion(prop)
            arrow = arrow_template.copy()
            arrow.next_to(point, UP, SMALL_BUFF)
            gaussian_arrows.add(arrow)

        for arrows in poisson_arrows, gaussian_arrows:
            self.play(
                ShowCreation(
                    arrows, 
                    lag_ratio = 0.5,
                    run_time = 2
                ),
                *[
                    ApplyMethod(pi.change, "thinking", arrows)
                    for pi in self.pi_creatures
                ]
            )
            self.wait()
        self.wait(2)

    ####

    def get_binomial(self):
        k_range = list(range(11))
        dists = [
            get_binomial_distribution(10, p)
            for p in (0.2, 0.8, 0.5)
        ]
        values_list = [
            list(map(dist, k_range))
            for dist in dists
        ]
        chart = BarChart(
            values = values_list[-1],
            bar_names = k_range
        )
        chart.set_height(self.chart_height)
        chart.values_list = values_list
        return chart

    def get_poisson(self):
        k_range = list(range(11))
        L = 2
        values = [
            np.exp(-L) * (L**k) / (scipy.special.gamma(k+1))
            for k in k_range
        ]
        chart = BarChart(
            values = values,
            bar_names = k_range,
            bar_colors = [RED, YELLOW]
        )
        chart.set_height(self.chart_height)
        title = TextMobject(
            "Poisson distribution \\\\",
            "$e^{-\\lambda}\\frac{\\lambda^k}{k!}$"
        )
        title.scale(0.75)
        title.move_to(chart, UP)
        title.shift(MED_SMALL_BUFF*RIGHT)
        title[0].shift(SMALL_BUFF*UP)
        chart.title = title

        return chart

    def get_gaussian(self):
        axes = VGroup(self.binomial.x_axis, self.binomial.y_axis).copy()
        graph = FunctionGraph(
            lambda x : 5*np.exp(-x**2),
            mark_paths_closed = True,
            fill_color = BLUE_E,
            fill_opacity = 1,
            stroke_color = BLUE,
        )
        graph.set_width(axes.get_width())
        graph.move_to(axes[0], DOWN)

        title = TextMobject(
            "Gaussian distribution \\\\ ",
            "$\\frac{1}{\\sqrt{2\\pi \\sigma^2}} e^{-\\frac{(x-\\mu)^2}{2\\sigma^2}}$"
        )
        title.scale(0.75)
        title.move_to(axes, UP)
        title.shift(MED_SMALL_BUFF*RIGHT)
        title[0].shift(SMALL_BUFF*UP)
        result = VGroup(axes, graph)
        result.title = title

        return result

class IndependencePatreonThanks(PatreonThanks):
    CONFIG = {
        "specific_patrons" : [
            "Ali Yahya",
            "Desmos",
            "Burt Humburg",
            "CrypticSwarm",
            "Juan Benet",
            "Mayank M. Mehrotra",
            "Lukas Biewald",
            "Samantha D. Suplee",
            "James Park",
            "Erik Sundell",
            "Yana Chernobilsky",
            "Kaustuv DeBiswas",
            "Kathryn Schmiedicke",
            "Karan Bhargava",
            "Yu Jun",
            "Dave Nicponski",
            "Damion Kistler",
            "Markus Persson",
            "Yoni Nazarathy",
            "Corey Ogburn",
            "Ed Kellett",
            "Joseph John Cox",
            "Dan Buchoff",
            "Luc Ritchie",
            "Tianyu Ge",
            "Ted Suzman",
            "Amir Fayazi",
            "Linh Tran",
            "Andrew Busey",
            "Michael McGuffin",
            "John Haley",
            "Mourits de Beer",
            "Ankalagon",
            "Eric Lavault",
            "Tomohiro Furusawa",
            "Boris Veselinovich",
            "Julian Pulgarin",
            "Jeff Linse",
            "Cooper Jones",
            "Ryan Dahl",
            "Mark Govea",
            "Robert Teed",
            "Jason Hise",
            "Meshal Alshammari",
            "Bernd Sing",
            "Nils Schneider",
            "James Thornton",
            "Mustafa Mahdi",
            "Mathew Bramson",
            "Jerry Ling",
            "Vecht",
            "Shimin Kuang",
            "Rish Kundalia",
            "Achille Brighton",
            "Ripta Pasay",
        ],
    }

class Thumbnail(DangerInProbability):
    def construct(self):
        n, p = 15, 0.5
        dist = get_binomial_distribution(n, p)
        values = np.array(list(map(dist, list(range(n+1)))))
        values *= 2
        chart = BarChart(
            values = values,
            label_y_axis = False,
            width = FRAME_WIDTH - 3,
            height = 1.5*FRAME_Y_RADIUS
        )
        chart.to_edge(DOWN)
        self.add(chart)


        warning = self.get_warning_sign()
        warning.set_height(2)
        warning.to_edge(UP)
        self.add(warning)


        words = TextMobject("Independence")
        words.scale(2.5)
        words.next_to(warning, DOWN)
        self.add(words)

























