From 887565a5dfb1041ab42ed6ed1214101642f03106 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 2 Aug 2017 11:56:47 -0700 Subject: [PATCH] End of independence project --- eop/independence.py | 414 +++++++++++++++++++++++++++++++++++++++--- topics/probability.py | 19 +- 2 files changed, 400 insertions(+), 33 deletions(-) diff --git a/eop/independence.py b/eop/independence.py index b9577477..14ea222b 100644 --- a/eop/independence.py +++ b/eop/independence.py @@ -56,11 +56,20 @@ def get_quiz(*questions): rect.shift(MED_SMALL_BUFF*DOWN) rect.highlight(WHITE) quiz = VGroup(rect, content) + quiz.questions = q_mobs quiz.scale(0.7) return quiz -def get_slot_group(bool_list, buff = MED_LARGE_BUFF, include_qs = True): +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) @@ -102,7 +111,7 @@ def get_slot_group(bool_list, buff = MED_LARGE_BUFF, include_qs = True): return slot_group def get_probability_of_slot_group(bool_list, conditioned_list = None): - filler_tex = "Fi"*len(bool_list) + filler_tex = "Fi"*max(len(bool_list), 3) if conditioned_list is None: result = TexMobject("P(", filler_tex, ")") else: @@ -735,6 +744,33 @@ class AssociatePatternsWithScores(BreakDownQuestionPatterns): self.play(row.highlight, YELLOW) self.dither(4) +class BeforeCounting(TeacherStudentsScene): + def construct(self): + triangle = PascalsTriangle(nrows = 7) + triangle.scale_to_fit_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), + LaggedStart(FadeIn, q_marks) + ) + self.dither(2) + class TemptingButWrongCalculation(BreakDownQuestionPatterns): def construct(self): self.add_title() @@ -745,6 +781,7 @@ class TemptingButWrongCalculation(BreakDownQuestionPatterns): 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)", "= ") @@ -807,6 +844,9 @@ class TemptingButWrongCalculation(BreakDownQuestionPatterns): ) self.dither(3) + self.question = question + self.rhs = rhs + class ThousandPossibleQuizzes(Scene): CONFIG = { "n_quiz_rows" : 25, @@ -820,6 +860,7 @@ class ThousandPossibleQuizzes(Scene): 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() @@ -852,8 +893,8 @@ class ThousandPossibleQuizzes(Scene): full_quizzes.arrange_submobjects(RIGHT) target_quizzes = VGroup(*quizzes[:len(full_quizzes)]) - self.add(full_quizzes) - self.dither() + for quiz in full_quizzes: + self.play(FadeIn(quiz, run_time = 3, submobject_mode = "lagged_start")) self.play( Transform(full_quizzes, target_quizzes), FadeIn(title) @@ -904,6 +945,43 @@ class ThousandPossibleQuizzes(Scene): 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_submobjects(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.dither() + self.play(FadeOut(little_rects)) + self.play(ShowCreation(big_rect)) + self.play( + FadeOut(big_rect), + FadeOut(question), + ) + self.dither() + def show_uncorrelated_division_by_second(self): top_split = self.splits[0] top_label = self.q1_split_labels[0] @@ -1168,6 +1246,86 @@ class ThousandPossibleQuizzes(Scene): 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.highlight_by_tex("0.925", YELLOW) + rhs.next_to(prob, RIGHT) + expression = VGroup(prob, rhs) + expression.scale_to_fit_width(2*SPACE_WIDTH - 1) + expression.center().to_edge(DOWN) + + self.play(Write(expression)) + self.dither() + +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_submobjects(RIGHT) + quizzes.to_edge(DOWN) + crosses = VGroup(*[ + Cross(quiz.questions[0]) + for quiz in quizzes + ]) + + for quiz in quizzes: + self.play(FadeIn(quiz)) + self.dither() + for cross in crosses: + self.play(ShowCreation(cross)) + self.dither() + +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.scale_to_fit_width(2*SPACE_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.highlight_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.dither() + class AccurateProductRule(SampleSpaceScene, ThreeDScene): def construct(self): self.setup_terms() @@ -1742,12 +1900,17 @@ class ProbablyWrong(TeacherStudentsScene): 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) @@ -1816,6 +1979,17 @@ class ShowTrueDistribution(PiCreatureScene): self.dither() self.play(FadeOut(bar_rect)) + def get_angry(self): + randy = self.randy + + self.play(randy.change, "angry") + self.dither(2) + self.play(PiCreatureSays( + randy, "It's not representative!", + target_mode = "pleading", + bubble_kwargs = {"fill_opacity" : 1} + )) + self.dither(2) ##### @@ -2031,9 +2205,48 @@ class CorrelationsWith35Percent(ThousandPossibleQuizzes): ) self.dither() +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.highlight_by_tex("A", BLUE) + formula.highlight_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.dither(2) + + #### + + def create_pi_creature(self): + self.randy = Randolph().to_edge(DOWN) + return self.randy + class NameBinomial(Scene): CONFIG = { - "flip_indices" : [1, 2, 4, 5, 7, 9], + "flip_indices" : [0, 2, 4, 5, 6, 7], } def construct(self): self.name_distribution() @@ -2178,10 +2391,11 @@ class NameBinomial(Scene): GrowFromCenter(brace), FadeIn(words) ) - for m1, m2 in (self.checkmarks, girls), (self.crosses, boys): + for m1, m2 in (self.crosses, boys), (self.checkmarks, girls): self.play(ReplacementTransform( m1, m2, - submobject_mode = "lagged_start" + submobject_mode = "lagged_start", + run_time = 3 )) self.dither() @@ -2230,6 +2444,18 @@ class NameBinomial(Scene): buff = SMALL_BUFF ) + prob = TexMobject( + "P(", "\\# \\text{Girls}", "=", "6", ")" + ) + prob.highlight_by_tex("Girls", MAROON_B) + arrow = Arrow(UP, ORIGIN, tip_length = 0.15) + arrow.highlight(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(LaggedStart( ShowCreation, girl_rects, @@ -2238,6 +2464,16 @@ class NameBinomial(Scene): )) self.dither() + self.play(Write(prob)) + self.play(LaggedStart( + Indicate, girls, + run_time = 3, + lag_ratio = 0.3, + rate_func = there_and_back + )) + self.play(FadeOut(prob)) + self.dither() + self.chart_rect = chart_rect self.girl_rects = girl_rects @@ -2413,30 +2649,39 @@ class ProbabilityOfAGivenBoyGirlPattern(CycleThroughPatterns): prob.submobjects[1] = pattern prob.next_to(self.count, DOWN, LARGE_BUFF) - gp = TexMobject("(0.49)").highlight(MAROON_B) - bp = TexMobject("(0.51)").highlight(BLUE) + gp = TexMobject("P(\\female)") + gp[2].highlight(MAROON_B) + bp = TexMobject("P(\\male)") + bp[2].highlight(BLUE) + gp_num = TexMobject("(0.49)").highlight(MAROON_B) + bp_num = TexMobject("(0.51)").highlight(BLUE) + gp_nums = VGroup() + bp_nums = VGroup() factored = VGroup() - gps = VGroup() - bps = VGroup() + factored_in_nums = VGroup() for i in range(10): if i in indices: - mob = gp.copy() - gps.add(mob) + num_mob = gp_num.copy() + gp_nums.add(num_mob) + p_mob = gp.copy() else: - mob = bp.copy() - bps.add(mob) - factored.add(mob) - factored.arrange_submobjects(RIGHT, buff = SMALL_BUFF) - factored.next_to(prob, DOWN, MED_LARGE_BUFF) - gps.save_state() - bps.save_state() + 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_submobjects(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.highlight_by_tex("0.49", MAROON_B) final_probability.highlight_by_tex("0.51", BLUE) - final_probability.next_to(factored, DOWN, LARGE_BUFF) + final_probability.next_to(factored_in_nums, DOWN, LARGE_BUFF) self.play(FadeIn(prob)) self.dither() @@ -2445,7 +2690,13 @@ class ProbabilityOfAGivenBoyGirlPattern(CycleThroughPatterns): run_time = 1.5, )) self.dither(2) - for group, tex in (gps, "0.49"), (bps, "0.51"): + self.play(ReplacementTransform( + factored, factored_in_nums, + run_time = 2, + submobject_mode = "lagged_start" + )) + self.dither(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( @@ -2510,7 +2761,7 @@ class GeneralBinomialDistributionValues(Scene): ) full_probability.next_to(chart, UP, aligned_edge = LEFT) - self.add(chart, full_probability) + self.add(chart) self.chart = chart self.full_probability = full_probability @@ -2541,6 +2792,12 @@ class GeneralBinomialDistributionValues(Scene): self.play(FadeIn(shown_prob)) self.dither() + self.play(LaggedStart( + FadeIn, self.full_probability, + run_time = 4, + lag_ratio = 0.5, + )) + self.dither() last_k = 6 for k in 3, 8, 5, 9, 6: self.play(Transform( @@ -2877,12 +3134,43 @@ class ButWhatsTheAnswer(TeacherStudentsScene): self.play(self.teacher.change, "pondering") self.dither(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.highlight(color) + quiz.scale(2) + + self.add(quiz) + self.dither() + 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.dither() + 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: " +\ @@ -2933,18 +3221,50 @@ class AssumeOrderDoesntMatter(Scene): self.prob_groups = prob_groups def mention_correlation(self): - everything = VGroup(*self.get_top_level_mobjects()) - question = TextMobject("But what is ``correlation''?") + assumption_group = VGroup(*self.get_top_level_mobjects()) + question = TextMobject( + "But what is ", "``correlation''", "?", + arg_separator = "" + ) question.highlight(BLUE) question.to_edge(UP) bottom = question.get_bottom() self.play( Write(question), - everything.next_to, bottom, DOWN, LARGE_BUFF + assumption_group.next_to, bottom, DOWN, LARGE_BUFF ) self.dither() + self.assumption_group = assumption_group + self.question = question + + def coming_soon(self): + self.play( + LaggedStart( + ApplyMethod, self.assumption_group, + lambda m : (m.shift, 2*SPACE_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.highlight, YELLOW + ) + self.play(Write(words)) + self.dither() + + + class FormulaCanBeRediscovered(PointOutSimplicityOfFormula): def construct(self): prob = self.get_probability_expression(full = False) @@ -3223,6 +3543,48 @@ class IndependencePatreonThanks(PatreonThanks): ], } +class Thumbnail(DangerInProbability): + def construct(self): + n, p = 15, 0.5 + dist = get_binomial_distribution(n, p) + values = np.array(map(dist, range(n+1))) + values *= 2 + chart = BarChart( + values = values, + label_y_axis = False, + width = 2*SPACE_WIDTH - 3, + height = 1.5*SPACE_HEIGHT + ) + chart.to_edge(DOWN) + self.add(chart) + + + warning = self.get_warning_sign() + warning.scale_to_fit_height(2) + warning.to_edge(UP) + self.add(warning) + + + words = TextMobject("Independence") + words.scale(2.5) + words.next_to(warning, DOWN) + self.add(words) + + + + + + + + + + + + + + + + diff --git a/topics/probability.py b/topics/probability.py index e5ac9ed8..01c8b9c5 100644 --- a/topics/probability.py +++ b/topics/probability.py @@ -307,7 +307,6 @@ class BarChart(VGroup): x_axis = Line(self.tick_width*LEFT/2, self.width*RIGHT) y_axis = Line(MED_LARGE_BUFF*DOWN, self.height*UP) ticks = VGroup() - labels = VGroup() heights = np.linspace(0, self.height, self.n_ticks+1) values = np.linspace(0, self.max_value, self.n_ticks+1) for y, value in zip(heights, values): @@ -315,15 +314,21 @@ class BarChart(VGroup): tick.scale_to_fit_width(self.tick_width) tick.move_to(y*UP) ticks.add(tick) - label = TexMobject(str(np.round(value, 2))) - label.scale_to_fit_height(self.y_axis_label_height) - label.next_to(tick, LEFT, SMALL_BUFF) - labels.add(label) y_axis.add(ticks) - self.add(x_axis, y_axis, labels) + self.add(x_axis, y_axis) self.x_axis, self.y_axis = x_axis, y_axis - self.y_axis_labels = labels + + if self.label_y_axis: + labels = VGroup() + for tick, value in zip(ticks, values): + label = TexMobject(str(np.round(value, 2))) + label.scale_to_fit_height(self.y_axis_label_height) + label.next_to(tick, LEFT, SMALL_BUFF) + labels.add(label) + self.y_axis_labels = labels + self.add(labels) + def add_bars(self, values): buff = float(self.width) / (2*len(values) + 1)