from helpers import * from mobject.tex_mobject import TexMobject from mobject import Mobject from mobject.image_mobject import ImageMobject from mobject.vectorized_mobject import * from animation.animation import Animation from animation.transform import * from animation.simple_animations import * from animation.playground import * from topics.geometry import * from topics.characters import * from topics.functions import * from topics.fractals import * from topics.number_line import * from topics.combinatorics import * from topics.numerals import * from topics.three_dimensions import * from topics.objects import * from topics.complex_numbers import * from topics.common_scenes import * from topics.probability import * from scene import Scene from scene.reconfigurable_scene import ReconfigurableScene from scene.zoomed_scene import * from camera import Camera from mobject.svg_mobject import * from mobject.tex_mobject import * from scene.scene import ProgressDisplay #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(*map(TextMobject, [ "%d. %s"%(i+1, question) for i, question in enumerate(questions) ])) q_mobs.arrange_submobjects( 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_submobjects(DOWN, buff = MED_SMALL_BUFF) rect = SurroundingRectangle(content, buff = MED_LARGE_BUFF) rect.shift(MED_SMALL_BUFF*DOWN) rect.highlight(WHITE) quiz = VGroup(rect, content) quiz.scale(0.7) return quiz def get_slot_group(bool_list, buff = MED_LARGE_BUFF, include_qs = True): lines = VGroup(*[ Line(ORIGIN, MED_LARGE_BUFF*RIGHT) for x in range(3) ]) lines.arrange_submobjects(RIGHT, buff = buff) if include_qs: labels = VGroup(*[ TextMobject("Q%d"%d) for d in range(1, 4) ]) else: labels = VGroup(*[VectorizedPoint() for d in range(3)]) 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 = SPACE_HEIGHT 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.highlight(GREEN) slot_group.shift(total_height*DOWN / (base**(i+1))) else: mob = TexMobject("\\times") mob.highlight(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 = "Filler" 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, LaggedStart(FadeIn, probability) ) self.dither() ##### def get_warning_sign(self): triangle = RegularPolygon(n = 3, start_angle = np.pi/2) triangle.set_stroke(RED, 12) triangle.scale_to_fit_height(2) bang = TextMobject("!") bang.scale_to_fit_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.highlight(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.dither() 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.dither() 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.dither() self.play(Blink(morty)) self.dither() 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.dither() 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.highlight_by_tex("k", BLUE) formula.highlight_by_tex("p", YELLOW) choose_part = formula.get_part_by_tex("choose") choose_part.highlight(WHITE) choose_part[-2].highlight(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 = range(n+1), ) chart.to_edge(LEFT) self.bar_chart = chart self.play(LaggedStart( FadeIn, VGroup(*it.chain(*chart)), run_time = 2 )) def add_p_slider(self): interval = UnitInterval(color = LIGHT_GREY) interval.scale_to_fit_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.scale_to_fit_height(0.25) triangle.move_to(interval.number_to_point(self.p), DOWN) label = TexMobject("p") label.next_to(triangle, UP, SMALL_BUFF) label.highlight(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.dither() def write_independence_assumption(self): assumption = TextMobject("Independence assumption") assumption.scale(1.2) assumption.next_to(self.formula, DOWN, MED_LARGE_BUFF, LEFT) assumption.highlight(GREEN_C) self.play(Write(assumption, run_time = 2)) self.dither() self.assumption = assumption def cross_out_assumption(self): cross = Cross(self.assumption) cross.highlight(GREY) self.bar_chart.save_state() self.play(ShowCreation(cross)) self.play(self.bar_chart.fade, 0.7) self.dither(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(map(dist, 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.highlight(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.dither(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.dither() self.quiz = quiz def ask_about_probabilities(self): probabilities, abbreviated_probabilities = [ VGroup(*[ TexMobject( "P(", s_tex, "=", str(score), ")", rhs ).highlight_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_submobjects( DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT ) group.to_corner(UP+LEFT) self.play( LaggedStart(FadeIn, probabilities, run_time = 3), self.quiz.scale_to_fit_height, 0.7*self.randy.get_height(), self.quiz.next_to, self.randy, RIGHT, self.randy.change, "confused", probabilities ) self.dither() self.probabilities = probabilities self.abbreviated_probabilities = abbreviated_probabilities def show_distribution(self): dist = get_binomial_distribution(3, 0.7) values = map(dist, range(4)) chart = BarChart( values, width = 7, bar_names = range(4) ) chart.to_edge(RIGHT) for short_p, bar in zip(self.abbreviated_probabilities, chart.bars): short_p.scale_to_fit_width(1.75*bar.get_width()) short_p.next_to(bar, UP) self.play( LaggedStart(Write, VGroup( *filter(lambda m : m is not chart.bars, chart) )), ) 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.dither() 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.highlight_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.dither() 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.dither() for new_group_group in slot_group_groups[1:]: self.play(Transform(group_group, new_group_group)) self.dither(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.dither() ####### 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(*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.highlight_by_tex("Score", GREEN) scores.add(score) score_group.organized = score_group.deepcopy() score_group.organized.arrange_submobjects(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_submobjects( 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.dither() self.play( ReplacementTransform( score_group.copy(), score_group.organized ), LaggedStart(FadeIn, score, run_time = 1) ) self.play(score_group.restore) self.dither() 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", LaggedStart(FadeIn, triangle, run_time = 4), ) self.play(row.highlight, YELLOW) self.dither(4) 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) 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_submobjects(RIGHT, SMALL_BUFF) rhs.next_to(lhs, RIGHT, SMALL_BUFF) for part, b in zip(rhs, slot_group.bool_list): part.highlight_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.highlight(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), LaggedStart(FadeIn, slot_group.content, run_time = 3), self.randy.change, "pondering" ) self.dither(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.dither() self.dither() self.play( Write(question), self.randy.change, "confused" ) self.dither(3) 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.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_submobjects(RIGHT) target_quizzes = VGroup(*quizzes[:len(full_quizzes)]) self.add(full_quizzes) self.dither() self.play( Transform(full_quizzes, target_quizzes), FadeIn(title) ) self.play( LaggedStart( FadeIn, quizzes, run_time = 3, lag_ratio = 0.2, ), Animation(full_quizzes, remover = True) ) self.dither() 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_submobjects(lambda p : p[0]) split.generate_target() split.target.shift(MED_LARGE_BUFF*vect) for quiz in split.target: quiz[0].highlight(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_submobjects(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.dither() self.splits = VGroup(top_split, bottom_split) self.q1_split_labels = labels 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].highlight(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_submobjects(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(LaggedStart( ApplyMethod, left_split, lambda m : (m.highlight, YELLOW), rate_func = there_and_back, lag_ratio = 0.2, )) self.dither() 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].highlight(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].highlight(YELLOW) new_equation.move_to(left_label_equation) self.play( MoveToTarget( movers, submobject_mode = "lagged_start", lag_factor = 4, run_time = 3, ), Transform(left_label_equation, new_equation) ) self.dither(2) self.play(Indicate(left_label_equation[0])) self.dither() 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].highlight(color) left_split.target.shift(LEFT) movers = VGroup(*left_split[-self.n_movers:]) movers.generate_target() for quiz in movers.target: quiz[1].highlight(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_submobjects(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].highlight(YELLOW) alt_equation.move_to(equation) self.play(top_group.to_edge, UP, SMALL_BUFF) self.play( bottom_label.shift, LEFT, *map(MoveToTarget, [left_split, right_split]) ) self.play(FadeIn(label)) self.dither() self.play( MoveToTarget( movers, submobject_mode = "lagged_start", run_time = 3, ), Transform(equation, alt_equation) ) self.dither() 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.highlight_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_submobjects(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), *map(Animation, [num1, num2]) ) self.remove(num1, num2) self.dither() 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].highlight(RED) for quiz in bottom_movers.target: quiz[0].highlight(GREEN) line = Line(UP, DOWN, color = YELLOW) line.scale_to_fit_height(self.quizzes.get_height()) line.next_to(bottom_movers.target, LEFT, MED_LARGE_BUFF, UP) self.revert_to_original_skipping_status() self.play(*map(MoveToTarget, both_movers)) self.play(ShowCreation(line)) self.play(FadeOut(line)) self.dither() self.play(both_movers.restore) self.dither() 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_submobjects(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].highlight(GREEN) for quiz in bottom_part: quiz[-1].highlight(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.dither() self.play(LaggedStart( ApplyMethod, all_right, lambda m : (m.highlight, YELLOW), rate_func = there_and_back, lag_ratio = 0.2, run_time = 2 )) self.dither(2) ##### def get_thousand_quizzes(self): rows = VGroup() for x in xrange(self.n_quiz_rows): quiz = VGroup(*[ Rectangle( height = SMALL_BUFF, width = 0.5*SMALL_BUFF ) for x in range(3) ]) quiz.arrange_submobjects(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_submobjects(RIGHT, buff = SMALL_BUFF) rows.add(row) rows.arrange_submobjects(DOWN, buff = SMALL_BUFF) quizzes = VGroup(*it.chain(*rows)) quizzes.scale_to_fit_height(self.quizzes_height) quizzes.to_edge(RIGHT) quizzes.shift(MED_LARGE_BUFF*DOWN) return quizzes class AccurateProductRule(SampleSpaceScene, ThreeDScene): def construct(self): self.setup_terms() self.add_sample_space() self.dither() 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_submobjects(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.dither() self.play(Write(self.terms[1].value)) self.dither() 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.dither() self.play(Write(self.terms[2].value)) self.dither() 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.dither() 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.dither() self.play(ReplacementTransform( label.copy(), self.terms[3] )) self.dither() 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.dither() 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_submobjects(DOWN) rect = SurroundingRectangle( VGroup(*equations[0][7:]+equations[-1][7:]), buff = SMALL_BUFF ) rect.shift(0.5*SMALL_BUFF*RIGHT) self.play(LaggedStart( FadeIn, equations, run_time = 5, lag_ratio = 0.3 )) self.dither() self.play(ShowCreation(rect, run_time = 2)) self.play(FadeOut(rect)) self.dither() 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.dither() class ShowIndependenceSymbolically(Scene): def construct(self): filler_tex = "Filler" rhs = TexMobject("=", "0.8") rhs.highlight_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.highlight(RED) words = TextMobject("Doesn't matter") words.highlight(RED) words.next_to(arrow, UP) self.add(rhs, lhs, arrow, words) self.dither() 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.dither() 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(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.highlight_by_tex("0.8", GREEN) rhs.highlight_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_submobjects(DOWN, buff = LARGE_BUFF) probabilities.center() self.play(Write(probabilities[0])) self.dither(2) for i in range(2): self.play(ReplacementTransform( probabilities[i].copy(), probabilities[i+1] )) self.dither() for group in point_8s, point_2s: self.play(LaggedStart( Indicate, group, rate_func = there_and_back, lag_ratio = 0.7 )) self.dither() def show_final_result(self): result = TexMobject( "P(", "\\text{Score} = %s"%self.score, ")", "=", *self.final_result_rhs_tex ) result.highlight_by_tex_to_color_map({ "0.8" : GREEN, "0.2" : RED, "Score" : YELLOW, }) result[-1].highlight(YELLOW) result.highlight_by_tex("0.8", GREEN) result.highlight_by_tex("0.2", RED) result.to_edge(UP) self.play(Write(result)) self.dither() 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_submobjects( DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT ) scores.shift(MED_LARGE_BUFF*UP) scores.to_edge(LEFT) for score in scores: score.highlight_by_tex_to_color_map({ "0.8" : GREEN, "0.2" : RED, }) score[-1].highlight(YELLOW) self.add(*scores[1:3]) self.scores = scores def add_scores_zero_and_three(self): self.p_slot_groups = VGroup() self.dither() 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(LaggedStart( FadeIn, group, run_time = 2, lag_ratio = 0.7, )) self.dither(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 = map(dist, range(4)) chart = BarChart( values, bar_names = 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].highlight(YELLOW) term.scale_to_fit_width(1.5*bar.get_width()) term.next_to(bar, UP) self.play( ReplacementTransform( value_mobs, chart.bars, submobject_mode = "lagged_start", run_time = 2 ) ) self.play( LaggedStart(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.dither(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 = map(dist, 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(*map(TexMobject, map(str, [1, 3, 3, 1]))) for num, bar in zip(nums, bars): num.next_to(bar, UP) bars_copy = bars.copy() self.play( LaggedStart(FadeIn, bars), LaggedStart(FadeIn, nums), ) self.dither(2) self.play(bars_copy.shift, -vect) self.play(ReplacementTransform( bars_copy, self.bar_chart.bars )) self.dither(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.highlight_by_tex("Correct", GREEN) new_prob.shift(SPACE_WIDTH*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.highlight(YELLOW) rhs.move_to(new_prob[-1]) alt_rhss.add(rhs) dist = get_binomial_distribution(3, p) values = map(dist, 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.dither() self.play(Transform(self.bar_chart, alt_charts[0])) self.dither() self.play(FadeOut(point_5_probs)) for rhs, chart in zip(alt_rhss, alt_charts)[1:]: self.play(Transform(new_prob[-1], rhs)) self.play(Transform(self.bar_chart, chart)) self.dither(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(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(LaggedStart(FadeIn, probs)) self.dither() 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.dither() class ShowTrueDistribution(PiCreatureScene): def construct(self): self.remove(self.randy) self.add_title() self.show_distributions() self.show_emotion() self.imagine_score_0() def add_title(self): title = TexMobject("P(", "\\text{Correct}", ")", "=", "0.65") title.to_edge(UP) title.highlight_by_tex("Correct", GREEN) self.add(title) self.title = title def show_distributions(self): dist = get_binomial_distribution(3, 0.65) values = np.array(map(dist, range(4))) alt_values = values + [0.2, 0, 0, 0.2] alt_values /= sum(alt_values) chart = BarChart(values, bar_names = 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.highlight(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.highlight(RED) arrows.gradient_highlight(BLUE, YELLOW) self.add(chart) self.play(*map(MoveToTarget, it.chain(bars, old_bars))) self.play( chart.change_bar_values, alt_values, *map(ShowCreation, arrows) ) self.dither(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.dither() self.play(ReplacementTransform( prob_rect, bar_rect )) self.dither() self.play(FadeOut(bar_rect)) ##### 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.highlight_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.highlight_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.dither() 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.dither(2) self.play( RemovePiCreatureBubble( self.teacher, target_mode = "guilty", ), PiCreatureSays(randy, "Wait!", target_mode = "surprised"), run_time = 1 ) self.dither(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_submobjects(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].highlight(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.dither() self.play( GrowFromCenter(brace), FadeIn(prop), *map(MoveToTarget, parts) ) self.dither() self.play( top_part.fade, 0.8, Transform(brace, term_brace), prop.next_to, term_brace, DOWN, ) self.dither() self.quizzes = bottom_part self.quizzes.sort_submobjects(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].highlight(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), *map(MoveToTarget, parts) ) self.dither() 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_submobjects(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].highlight(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), *map(MoveToTarget, parts) ) self.dither() self.play( Transform(brace, term_brace), prop.next_to, term_brace, DOWN, ) self.play(top_part.fade, 0.8) self.dither() 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.dither() class NameBinomial(Scene): CONFIG = { "flip_indices" : [1, 2, 4, 5, 7, 9], } 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 = map(dist, range(n+1)) chart = BarChart(values, bar_names = range(n+1)) chart.to_edge(LEFT) charts.add(chart) probability = TexMobject( "P(", "\\checkmark", ")", "=", str(p) ) probability.highlight_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.highlight_by_tex("p", YELLOW) formula.highlight_by_tex("k", GREEN) choose_part = formula.get_part_by_tex("choose") choose_part.highlight(WHITE) choose_part[-2].highlight(GREEN) formula.to_corner(UP+RIGHT) self.add(charts[0], probability) self.dither() self.play(Write(title)) self.dither() self.play(ReplacementTransform(*charts)) self.play(Write(formula)) self.dither() 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").highlight(GREEN) for x in range(n) ]) checkmarks.arrange_submobjects(DOWN, buff = 0.3) crosses = VGroup() arrows = VGroup() for checkmark in checkmarks: cross = TexMobject("\\times") cross.highlight(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(*filter( lambda m : m not in full_checks, checkmarks )) self.play(*[ LaggedStart( Write, mob, run_time = 3, lag_ratio = 0.3 ) for mob in full_group ]) self.dither() self.play( LaggedStart( 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.dither() 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.checkmarks, girls), (self.crosses, boys): self.play(ReplacementTransform( m1, m2, submobject_mode = "lagged_start" )) self.dither() 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 = map(dist, range(11)) self.play( Transform(checkmark, female), Transform(p_mob, new_p_mob), ) self.play(self.chart.change_bar_values, values) self.dither() 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 ) self.play(ShowCreation(chart_rect)) self.play(LaggedStart( ShowCreation, girl_rects, run_time = 2, lag_ratio = 0.5, )) self.dither() 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.dither() self.play( ReplacementTransform( VGroup(probability.copy()), probability_copies ), FadeOut(self.children_brace), FadeOut(self.n_children_words), ) self.dither() 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.dither() ## def get_male(self): return TexMobject("\\male").scale(1.3).highlight(BLUE) def get_female(self): return TexMobject("\\female").scale(1.3).highlight(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.highlight_by_tex("male", BLUE) question.highlight_by_tex("female", MAROON_B) question.scale_to_fit_width(2*SPACE_WIDTH - 1) question.to_edge(UP, buff = LARGE_BUFF) self.add(question) all_combinations = list(it.combinations(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.dither(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.highlight(GREEN) computation = TexMobject( "=\\frac{%s}{%s}"%( "\\cdot ".join(map(str, range(10, 4, -1))), "\\cdot ".join(map(str, 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.dither(2) self.play( ten_choose_six.next_to, computation.copy(), LEFT, Write(VGroup(computation, rhs)) ) self.dither() 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("(0.49)").highlight(MAROON_B) bp = TexMobject("(0.51)").highlight(BLUE) factored = VGroup() gps = VGroup() bps = VGroup() for i in range(10): if i in indices: mob = gp.copy() gps.add(mob) 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() 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) self.play(FadeIn(prob)) self.dither() self.play(ReplacementTransform( pattern.copy(), factored, run_time = 1.5, )) self.dither(2) for group, tex in (gps, "0.49"), (bps, "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.dither() self.play(group.restore) self.dither() 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.highlight_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.dither() 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 = map(dist, range(self.n+1)) chart = BarChart( values, bar_names = 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, full_probability) 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.dither() 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.dither(2) last_k = k self.shown_prob = shown_prob def compare_to_pascal_row(self): triangle = PascalsTriangle(nrows = 11) triangle.scale_to_fit_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.gradient_highlight(BLUE, YELLOW) self.play( LaggedStart(FadeIn, triangle), FadeOut(self.shown_prob) ) self.play( last_row.gradient_highlight, BLUE, YELLOW, Write(ten_choose_ks, run_time = 2) ) self.dither() self.play(ApplyWave(self.chart.bars, direction = UP)) self.play(FocusOn(last_row)) self.play(LaggedStart( ApplyMethod, last_row, lambda m : (m.scale_in_place, 1.2), rate_func = there_and_back, )) self.dither() 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_submobjects(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.dither(2) self.play( bars.restore, *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 = map(dist, range(alt_n + 1)) alt_chart = BarChart( values, bar_names = 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.dither(2) self.play(Transform(self.full_probability, alt_probs[1])) self.dither() def play_with_p_value(self): p = self.p interval = UnitInterval(color = WHITE) interval.scale_to_fit_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.scale_to_fit_height(0.25) triangle.move_to(interval.number_to_point(p), DOWN) p_mob = TexMobject("p") p_mob.highlight(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.dither() for new_p in new_p_values: p = new_p dist = get_binomial_distribution(self.alt_n, p) values = map(dist, range(self.alt_n + 1)) self.play( self.chart.change_bar_values, values, triangle.move_to, interval.number_to_point(p), DOWN ) self.dither() ####### 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.highlight_by_tex_to_color_map(color_map) choose_part = result.get_part_by_tex("choose") choose_part.highlight(WHITE) VGroup(*choose_part[1:1+len(n)]).highlight(color_map[n]) VGroup(*choose_part[-1-len(k):-1]).highlight(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.dither() self.student_says( "Simpler than I feared", target_mode = "hooray", student_index = 0, added_anims = [prob.to_corner, UP+RIGHT] ) self.dither() self.teacher_says("Due to \\\\ independence") self.dither(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.force_skipping() 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?") self.play( GrowFromCenter(brace), Write(words) ) self.dither(2) 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.highlight(GREEN) indices_to_follow = [1, 4, 5, 7] self.revert_to_original_skipping_status() self.play(ShowCreation(top_rect)) self.play(*self.get_arrow_flip_anims([0])) self.dither() self.play(*self.get_arrow_flip_anims(indices_to_follow)) self.dither() def adjust_chart(self): pass ###### 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 = np.linalg.norm(c - end) to_start = np.linalg.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 [ LaggedStart( MoveToTarget, mover, **kwargs ) for mover in movers ]