from manimlib.imports import * SICKLY_GREEN = "#9BBD37" class WomanIcon(SVGMobject): CONFIG = { "fill_color": GREY_B, "stroke_width": 0, } def __init__(self, **kwargs): super().__init__("woman_icon", **kwargs) self.remove(self[0]) class PersonIcon(SVGMobject): CONFIG = { "fill_color": GREY_B, "stroke_width": 0, } def __init__(self, **kwargs): super().__init__("person", **kwargs) class Population(VGroup): def __init__(self, count, **kwargs): super().__init__(**kwargs) icon = PersonIcon() self.add(*(icon.copy() for x in range(count))) self.arrange_in_grid() def get_covid_clipboard(disease_name="SARS\\\\CoV-2", color=GREEN): clipboard = SVGMobject("clipboard") clipboard.set_stroke(width=0) clipboard.set_fill(interpolate_color(GREY_BROWN, WHITE, 0.5), 1) clipboard.set_width(2.5) result = TextMobject( "+\\\\", disease_name + "\\\\", "Detected" ) result[0].scale(1.5, about_edge=DOWN) result[0].set_fill(color) result[0].set_stroke(color, 2) result[-1].set_fill(color) result.set_width(clipboard.get_width() * 0.7) result.move_to(clipboard) result.shift(0.2 * DOWN) clipboard.add(result) return clipboard # Scenes class MathAsDesign(Scene): def construct(self): # Setup design_word = TextMobject("Designed?", font_size=72) design_word.to_edge(UP) e_formula = TexMobject( "e", "^{\\pi", "i}", "=", "-1", font_size=96, ) e_formula[1].set_color(GREY_B) e_formula[2].set_color(YELLOW) e_formula[4].set_color(BLUE) l_formula = TexMobject( "\\sum_{n = 0}^{\\infty} \\frac{(-1)^n}{2n + 1} = \\frac{\\pi}{4}", font_size=72, ) buff = 1.5 formulas = VGroup(e_formula, l_formula) formulas.arrange(DOWN, buff=buff) formulas.next_to(design_word, DOWN, buff=buff) original_ly = l_formula.get_y() l_formula.center() self.play(Write(l_formula, run_time=1)) self.wait() self.play(FadeIn(design_word, 0.2 * UP, scale=1.1)) self.wait() alt_l_formula = VGroup( VGroup( TexMobject("\\pm").set_height(0.8), TexMobject("k \\text{ odd}", font_size=36) ).arrange(DOWN, SMALL_BUFF), TexMobject("\\frac{1}{k}"), TexMobject("="), VGroup( Sector( angle=PI / 2, fill_color=BLUE, fill_opacity=1, stroke_color=WHITE, stroke_width=2, ), Sector( start_angle=PI / 2, angle=3 * PI / 2, fill_color=GREY_E, fill_opacity=1, stroke_color=WHITE, stroke_width=2, ), ).set_height(1) ) alt_l_formula.arrange(RIGHT) alt_l_formula.match_height(l_formula) alt_l_formula.next_to(l_formula.get_center(), RIGHT, LARGE_BUFF) self.play( l_formula.next_to, l_formula.get_center(), LEFT, LARGE_BUFF, FadeIn(alt_l_formula, shift=2 * RIGHT) ) self.wait(2) # Euler's formula e_formula.next_to(e_formula.get_y() * UP, LEFT, buff=1.5) self.play( l_formula.scale, 0.7, l_formula.set_y, original_ly, alt_l_formula.scale, 0.7, alt_l_formula.set_y, original_ly, FadeIn(e_formula, scale=1.1) ) alt_e_formula = VGroup( TextMobject("exp", font_size=96), TexMobject("(", font_size=96), Arrow(0.5 * RIGHT, 0.5 * LEFT, path_arc=PI, buff=0, color=GREY_B), Dot(radius=0.05, color=WHITE), Vector(0.75 * UP, fill_color=YELLOW), TexMobject(")", font_size=96), TexMobject("=", font_size=96), Vector(LEFT, fill_color=BLUE), ) alt_e_formula.arrange(RIGHT, buff=MED_SMALL_BUFF) alt_e_formula[0].shift(0.15 * RIGHT) alt_e_formula[4:].shift(0.15 * LEFT) alt_e_formula.scale(0.8) alt_e_formula.next_to(e_formula.get_y() * UP, RIGHT, buff=1.0) alt_e_formula.shift(0.2 * DOWN) anims = [] for i, j in enumerate([0, 2, 4, 6, 7]): src = e_formula[i].copy() dst = alt_e_formula[j] src.generate_target() src.target.replace(dst, stretch=True) src.target.set_opacity(0) dst.save_state() dst.replace(src, stretch=True) dst.set_opacity(0) anims.append(MoveToTarget(src, remover=True)) anims.append(Restore(dst)) anims.extend([ FadeIn(alt_e_formula[1], shift=0.5 * UP, scale=3), FadeIn(alt_e_formula[5], shift=0.5 * UP, scale=3), GrowFromPoint(alt_e_formula[3], e_formula[1:3].get_center()), ]) self.play(*anims) self.wait() class BayesRuleAndMedicalTests(Scene): def construct(self): # Add title title = TextMobject("Bayes' rule", font_size=72) title.to_edge(UP) h_line = Underline(title) h_line.scale(1.5) h_line.set_stroke(GREY_B, 2) self.add(title) self.wait() # Show rule formula = TexMobject( "P(H|E) = {P(H)P(E|H) \\over P(E)}", tex_to_color_map={ "H": YELLOW, "E": BLUE, }, font_size=60 ) h_part = formula.get_part_by_tex("H") hyp_word = TextMobject("Hypothesis", color=YELLOW) hyp_word.next_to(h_part, UP, LARGE_BUFF) hyp_arrow = Arrow(hyp_word.get_bottom(), h_part.get_top(), buff=0.1) e_part = formula.get_part_by_tex("E") ev_word = TextMobject("Evidence", color=BLUE) ev_word.next_to(e_part, DOWN, LARGE_BUFF) ev_arrow = Arrow(ev_word.get_top(), e_part.get_bottom(), buff=0.1) self.play( ShowCreation(h_line), FadeIn(formula, scale=1.1), ) self.play( LaggedStart( FadeIn(hyp_word, shift=0.25 * UP, scale=1.1), FadeIn(ev_word, shift=0.25 * DOWN, scale=1.1), ), LaggedStart( GrowArrow(hyp_arrow), GrowArrow(ev_arrow), ), ) self.wait() formula_annoations = VGroup(hyp_word, hyp_arrow, ev_word, ev_arrow) # Randys pis = VGroup(Randolph(), Randolph()) pis.set_height(2) pis[1].flip() pis[1].set_color(TEAL_E) pis.arrange(RIGHT, buff=5) pis.to_corner(DL) self.play( VFadeIn(pis[0]), pis[0].change, "hooray", formula ) self.play(Blink(pis[0])) self.play( VFadeIn(pis[1]), pis[1].change, "maybe", formula ) self.play(Blink(pis[1])) self.wait() # Sweep aside title.add(h_line) formula.generate_target() formula.target.scale(0.6) formula.target.to_corner(DR) self.play( VFadeOut(formula_annoations), MaintainPositionRelativeTo(formula_annoations, formula), MoveToTarget(formula), title.scale, 0.7, title.next_to, formula.target, UP, FadeOut(pis, DOWN), ) # Mention paradox paradox_name = TextMobject("Medical Test Paradox") paradox_name.to_corner(UL) paradox_name.shift(MED_SMALL_BUFF * DOWN) paradox_line = Underline(paradox_name) paradox_line.set_stroke(GREY_B, 2) self.play( FadeIn(paradox_name, lag_ratio=0.1), ShowCreation(paradox_line), ) self.wait() # Show high accuracy accuracy_words = TextMobject("High accuracy") accuracy_words.next_to(paradox_line, DOWN, MED_LARGE_BUFF) accuracy_words.set_color(GREEN) population = Population(100) population.arrange_in_grid(buff=LARGE_BUFF) population.set_height(5) population.next_to(accuracy_words, DOWN) marks = VGroup() for icon in population: icon.generate_target() if random.random() < 0.05: mark = Exmark() icon.target.set_opacity(0.5) else: mark = Checkmark() mark.set_height(icon.get_height() / 2) mark.move_to(icon.get_corner(UL)) marks.add(mark) self.play( FadeIn(accuracy_words), FadeIn(population, lag_ratio=0.01) ) self.play( ShowIncreasingSubsets(marks, run_time=2), LaggedStartMap(MoveToTarget, population, run_time=2), ) self.wait() # Show test result randy = Randolph(height=2) randy.move_to(DOWN) clipboard = get_covid_clipboard("Virus", color=YELLOW) clipboard.set_height(2) clipboard.next_to(randy.get_corner(UR), RIGHT) self.play( VFadeIn(randy), randy.change, "guilty", clipboard, FadeIn(clipboard, shift=0.25 * UL, scale=1.5) ) self.play(randy.change, "horrified", clipboard) self.play(Blink(randy)) self.wait() # Show low predictive value prob_expression = TexMobject( "P(\\text{Sick} | +) = ", tex_to_color_map={ "+": YELLOW, "\\text{Sick}": GREY_A, } ) prob = DecimalNumber(0.9, font_size=60) prob.next_to(prob_expression, RIGHT) prob.set_color(WHITE) prob.set_opacity(0) prob_expression.add(prob) p_line = Underline(prob) p_line.shift(0.1 * DOWN) p_line.scale(1.5) p_line.set_stroke(WHITE, 2) prob_expression.add(p_line) prob_expression.next_to(VGroup(randy, clipboard), UP) self.play( FadeIn(prob_expression), randy.change, "pondering", prob_expression ) self.play( ChangeDecimalToValue(prob, 0.09), UpdateFromAlphaFunc(prob, lambda m, a: m.set_opacity(a)), run_time=2 ) self.play(Blink(randy)) self.play( randy.change, "confused", prob, ChangeDecimalToValue(prob, 0.01), ) self.play(Blink(randy)) self.wait() # Get rid of medical test stuff # Mention Bayes factors self.embed() class SamplePopulationBreastCancer(Scene): def construct(self): # Introduce population title = TextMobject( "Sample of ", "$1{,}000$", " women", font_size=72, ) title.add(Underline(title, color=LIGHT_GREY)) title.to_edge(UP, buff=MED_SMALL_BUFF) self.add(title) woman = WomanIcon() globals()['woman'] = woman population = VGroup(*[woman.copy() for x in range(1000)]) population.arrange_in_grid( 25, 40, buff=LARGE_BUFF, fill_rows_first=False, ) population.set_height(6) population.next_to(title, DOWN) counter = Integer(1000, edge_to_fix=UL) counter.replace(title[1]) counter.set_value(0) title[1].set_opacity(0) self.play( ShowIncreasingSubsets(population), ChangeDecimalToValue(counter, 1000), run_time=5 ) self.remove(counter) title[1].set_opacity(1) self.wait() # Show true positives rects = VGroup(Rectangle(), Rectangle()) rects.set_height(6) rects[0].set_width(4, stretch=True) rects[1].set_width(8, stretch=True) rects[0].set_stroke(YELLOW, 3) rects[1].set_stroke(GREY, 3) rects.arrange(RIGHT) rects.center().to_edge(DOWN, buff=MED_SMALL_BUFF) positive_cases = population[:10] negative_cases = population[10:] positive_cases.generate_target() positive_cases.target.move_to(rects[0]) positive_cases.target.set_color(YELLOW) negative_cases.generate_target() negative_cases.target.set_height(rects[1].get_height() * 0.8) negative_cases.target.move_to(rects[1]) positive_words = TextMobject(r"1\% ", "Have breast cancer", font_size=36) positive_words.set_color(YELLOW) positive_words.next_to(rects[0], UP, SMALL_BUFF) negative_words = TextMobject(r"99\% ", "Do not have cancer", font_size=36) negative_words.set_color(GREY_B) negative_words.next_to(rects[1], UP, SMALL_BUFF) self.play( MoveToTarget(positive_cases), MoveToTarget(negative_cases), Write(positive_words, run_time=1), Write(negative_words, run_time=1), FadeIn(rects), ) self.wait() # Show screening scan_lines = VGroup(*( Line( # FRAME_WIDTH * LEFT / 2, FRAME_HEIGHT * DOWN / 2, icon.get_center(), stroke_width=1, stroke_color=interpolate_color(BLUE, GREEN, random.random()) ) for icon in population )) self.play( LaggedStartMap( ShowCreationThenFadeOut, scan_lines, lag_ratio=1 / len(scan_lines), run_time=3, ) ) self.wait() # Test results on cancer population tpr_words = TextMobject("9 True positives", font_size=36) fnr_words = TextMobject("1 False negative", font_size=36) tnr_words = TextMobject("901 True negatives", font_size=36) fpr_words = TextMobject("89 False positives", font_size=36) tpr_words.set_color(GREEN_B) fnr_words.set_color(RED_D) tnr_words.set_color(RED_B) fpr_words.set_color(GREEN_D) tp_cases = positive_cases[:9] fn_cases = positive_cases[9:] tpr_words.next_to(tp_cases, UP) fnr_words.next_to(fn_cases, DOWN) signs = VGroup() for woman in tp_cases: sign = TexMobject("+") sign.set_color(GREEN_B) sign.match_height(woman) sign.next_to(woman, RIGHT, SMALL_BUFF) woman.sign = sign signs.add(sign) for woman in fn_cases: sign = TexMobject("-") sign.set_color(RED) sign.match_width(signs[0]) sign.next_to(woman, RIGHT, SMALL_BUFF) woman.sign = sign signs.add(sign) boxes = VGroup() for n, woman in enumerate(positive_cases): box = SurroundingRectangle(woman, buff=0) box.set_stroke(width=2) if woman in tp_cases: box.set_color(GREEN) else: box.set_color(RED) woman.box = box boxes.add(box) self.play( FadeIn(tpr_words, shift=0.2 * UP), ShowIncreasingSubsets(signs[:9]), ShowIncreasingSubsets(boxes[:9]), ) self.wait() self.play( FadeIn(fnr_words, shift=0.2 * DOWN), Write(signs[9:]), ShowCreation(boxes[9:]), ) self.wait() # Test results on cancer-free population negative_cases.sort(lambda p: -p[1]) num_fp = int(len(negative_cases) * 0.09) fp_cases = negative_cases[:num_fp] tn_cases = negative_cases[num_fp:] new_boxes = VGroup() for n, woman in enumerate(negative_cases): box = SurroundingRectangle(woman, buff=0) box.set_stroke(width=2) if woman in fp_cases: box.set_color(GREEN) else: box.set_color(RED) woman.box = box new_boxes.add(box) fpr_words.next_to(fp_cases, UP, buff=SMALL_BUFF) tnr_words.next_to(tn_cases, DOWN, buff=0.2) self.play( FadeIn(fpr_words, shift=0.2 * UP), ShowIncreasingSubsets(new_boxes[:num_fp]) ) self.wait() self.play( FadeIn(tnr_words, shift=0.2 * DOWN), ShowIncreasingSubsets(new_boxes[num_fp:]) ) self.wait() # Consolidate boxes self.remove(boxes, new_boxes, population) for woman in population: woman.add(woman.box) self.add(population) # Limit view to positive cases for cases, nr, rect in zip([tp_cases, fp_cases], [3, 7], rects): cases.save_state() cases.generate_target() for case in cases.target: case[-1].set_stroke(width=3) case[-1].scale(1.1) cases.target.arrange_in_grid( n_rows=nr, buff=0.5 * cases[0].get_width() ) cases.target.scale(0.5 / cases.target[0].get_height()) cases.target.move_to(rect) fp_cases.target.shift(0.4 * DOWN) positive_words.save_state() negative_words.save_state() tpr_words.save_state() fpr_words.save_state() self.play( MoveToTarget(tp_cases), MoveToTarget(fp_cases), tpr_words.next_to, tp_cases.target, UP, fpr_words.next_to, fp_cases.target, UP, FadeOut(signs), positive_words[0].set_opacity, 0, negative_words[0].set_opacity, 0, positive_words[1].match_x, rects[0], negative_words[1].match_x, rects[1], LaggedStart( FadeOut(fn_cases, shift=DOWN), FadeOut(fnr_words, shift=DOWN), FadeOut(tn_cases, shift=DOWN), FadeOut(tnr_words, shift=DOWN), ), ) self.wait() # Emphasize groups counts self.play( ShowCreationThenFadeOut(SurroundingRectangle( tpr_words[0][:1], stroke_width=2, stroke_color=WHITE, buff=0.05, )), LaggedStartMap(Indicate, tp_cases, color=YELLOW, lag_ratio=0.3, run_time=1), ) self.wait() self.play( ShowCreationThenFadeOut(SurroundingRectangle( fpr_words[0][:2], stroke_width=2, stroke_color=WHITE, buff=0.05, )), LaggedStartMap( Indicate, fp_cases, color=GREEN_A, lag_ratio=0.05, run_time=3 ) ) self.wait() # Final equation equation = TexMobject( "P(", "\\text{Have cancer }", "|", "\\text{ positive test})", "\\approx", "\\frac{9}{9 + 89}", "\\approx \\frac{1}{11}" ) equation.set_color_by_tex("cancer", YELLOW) equation.set_color_by_tex("positive", GREEN) equation.to_edge(UP, buff=SMALL_BUFF) self.play( FadeIn(equation[:-1], shift=UP), FadeOut(title, shift=UP), ) self.wait() self.play(Write(equation[-1])) self.wait() # Label PPV frame = self.camera.frame frame.save_state() ppv_words = TextMobject( "Positive\\\\", "Predictive\\\\", "Value\\\\", alignment="", ) ppv_words.next_to(equation, RIGHT, LARGE_BUFF, DOWN) for word in ppv_words: word[0].set_color(BLUE) ppv_rhs = TexMobject( "={\\text{TP} \\over \\text{TP} + \\text{FP}}", tex_to_color_map={ "\\text{TP}": GREEN_B, "\\text{FP}": GREEN_C, } ) ppv_rhs.next_to(ppv_words, RIGHT) ppv_rhs.shift(1.5 * LEFT) self.play(frame.scale, 1.1, {"about_edge": DL}) self.play(ShowIncreasingSubsets(ppv_words)) self.wait() self.play( equation.shift, 1.5 * LEFT + 0.5 * UP, ppv_words.shift, 1.5 * LEFT, FadeIn(ppv_rhs, lag_ratio=0.1), frame.scale, 1.1, {"about_edge": DL}, ) self.wait() # Go back to earlier state self.play( frame.restore, frame.shift, 0.5 * DOWN, LaggedStartMap(FadeOut, VGroup(equation, ppv_words, ppv_rhs)), LaggedStartMap(Restore, VGroup( tpr_words, tp_cases, fpr_words, fp_cases, )), run_time=3, ) self.play( LaggedStartMap(FadeIn, VGroup( fnr_words, fn_cases, tnr_words, tn_cases, )), ) self.wait() # Fade rects fade_rects = VGroup(*( BackgroundRectangle( VGroup(rect, words), fill_opacity=0.9, fill_color=BLACK, buff=SMALL_BUFF, ) for rect, words in zip(rects, [positive_words, negative_words]) )) # Sensitivity sens_eq = TexMobject( "\\text{Sensitivity}", "= {9 \\over 10}", "= 90\\%" ) sens_eq.next_to(rects[0], LEFT, MED_LARGE_BUFF, aligned_edge=UP) sens_eq.shift(DOWN) fnr_eq = TexMobject( "\\text{False Negative Rate}", "= 10\\%" ) fnr_eq.set_color(RED) fnr_eq.scale(0.9) equiv = TexMobject("\\Leftrightarrow") equiv.scale(1.5) equiv.rotate(90 * DEGREES) equiv.next_to(sens_eq, DOWN, MED_LARGE_BUFF) fnr_eq.next_to(equiv, DOWN, MED_LARGE_BUFF) self.play( frame.shift, 5 * LEFT, FadeIn(fade_rects[1]), Write(sens_eq[0]), ) self.wait() self.play( TransformFromCopy(tpr_words[0][0], sens_eq[1][1]), Write(sens_eq[1][0]), Write(sens_eq[1][2:]), ) self.play(Write(sens_eq[2])) self.wait() self.play( FadeIn(equiv, shift=0.5 * DOWN), FadeIn(fnr_eq, shift=1.0 * DOWN), ) self.wait() # Transition to right side fade_rects[0].stretch(5, 0, about_edge=RIGHT) self.play( ApplyMethod(frame.shift, 10 * RIGHT, run_time=4), FadeIn(fade_rects[0], run_time=2), FadeOut(fade_rects[1], run_time=2), ) # Specificity spec_eq = TexMobject( "\\text{Specificity}", "= {901 \\over 990}", "\\approx 91\\%" ) spec_eq.next_to(rects[1], RIGHT, MED_LARGE_BUFF, aligned_edge=DOWN) spec_eq.shift(UP) fpr_eq = TexMobject( "\\text{False Positive Rate}", "= 9\\%" ) fpr_eq.set_color(GREEN) fpr_eq.scale(0.9) equiv2 = TexMobject("\\Leftrightarrow") equiv2.scale(1.5) equiv2.rotate(90 * DEGREES) equiv2.next_to(spec_eq, UP, MED_LARGE_BUFF) fpr_eq.next_to(equiv2, UP, MED_LARGE_BUFF) self.play(Write(spec_eq[0])) self.wait() self.play( Write(spec_eq[1][0]), TransformFromCopy( tnr_words[0][:3], spec_eq[1][1:4], run_time=2, path_arc=30 * DEGREES, ), Write(spec_eq[1][4:]), ) self.wait() self.play(Write(spec_eq[2])) self.wait() self.play( FadeIn(equiv2, shift=0.5 * UP), FadeIn(fpr_eq, shift=1.0 * UP), ) self.wait() # Reset to show both kinds of accuracy eqs = [sens_eq, spec_eq] for eq, word in zip(eqs, [positive_words, negative_words]): eq.generate_target() eq.target[1].set_opacity(0) eq.target[2].move_to(eq.target[1], LEFT), eq.target.next_to(word, UP, buff=0.3) self.play( FadeOut(fade_rects[0]), frame.shift, 5 * LEFT, frame.scale, 1.1, {"about_edge": DOWN}, MoveToTarget(sens_eq), MoveToTarget(spec_eq), *map(FadeOut, (fnr_eq, fpr_eq, equiv, equiv2)), run_time=2, ) self.wait() self.play( VGroup( fn_cases, fnr_words, fp_cases, fpr_words, ).set_opacity, 0.2, rate_func=there_and_back_with_pause, run_time=3 ) class AskWhatTheParadoxIs(TeacherStudentsScene): def construct(self): # Image image = ImageMobject("ppv_image") image.replace(self.screen) image.set_opacity(0) outline = SurroundingRectangle(image, buff=0) outline.set_stroke(WHITE, 2) outline.set_fill(BLACK, 1) image = Group(outline, image) image.set_height(3.5, about_edge=UL) # What's the paradox? self.add(image) self.student_says( "How's that\\\\a paradox?", target_mode="sassy", look_at_arg=self.teacher.eyes, student_index=2, added_anims=[ self.students[0].change, "pondering", image, self.students[1].change, "pondering", image, ] ) self.play(self.teacher.change, 'guilty') self.wait(3) # Consider test accuracy self.teacher_says( "Consider the\\\\", "test accuracy" ) self.wait(3) # Test accuracy split lower_words = self.teacher.bubble.content[1].copy() lower_words.unlock_triangulation() top_words = TextMobject("Test Accuracy", font_size=72) top_words.to_corner(UR) top_words.shift(LEFT) sens_spec = VGroup( TextMobject("Sensitivity", color=YELLOW), TextMobject("Specificity", color=BLUE_D), ) sens_spec.scale(1) sens_spec.arrange(RIGHT, buff=1.0) sens_spec.next_to(top_words, DOWN, LARGE_BUFF) lines = VGroup(*( Line(top_words.get_bottom(), word.get_top(), buff=0.1, color=word.get_color()) for word in sens_spec )) globals()['top_words'] = top_words self.play( TransformFromCopy(lower_words, top_words[0]), RemovePiCreatureBubble( self.teacher, target_mode="raise_right_hand", look_at_arg=top_words, ), *(ApplyMethod(pi.look_at, top_words) for pi in self.students) ) self.play( LaggedStartMap(ShowCreation, lines), LaggedStart(*( FadeIn(word, shift=word.get_center() - top_words.get_center()) for word in sens_spec )), run_time=1, ) self.wait(4) class MedicalTestsMatter(Scene): def construct(self): randy = Randolph(height=3) randy.to_corner(DL) randy.shift(2 * RIGHT) clipboard = get_covid_clipboard() clipboard.next_to(randy, RIGHT) clipboard.set_y(0) clipboard_words = VGroup( TexMobject("-", color=RED), TextMobject("SARS\\\\CoV-2"), TextMobject("Not Detected", color=RED), ) for m1, m2 in zip(clipboard_words, clipboard[2]): m1.replace(m2, 0) clipboard.remove(clipboard[2]) self.add(randy) self.play( FadeIn(clipboard, shift=LEFT), randy.change, 'guilty' ) self.play( Write(clipboard_words), randy.change, "hooray", clipboard, ) self.play(Blink(randy)) question = TextMobject("What does\\\\really this mean?") question.next_to(clipboard, RIGHT, buff=1.5) question.shift(1.5 * UP) q_arrow = Arrow( question.get_bottom(), clipboard.get_right(), path_arc=-30 * DEGREES, buff=0.3, ) self.play( FadeIn(question, shift=0.25 * UP), DrawBorderThenFill(q_arrow), randy.change, "confused", ) self.play(randy.look_at, clipboard.get_top()) self.play(Blink(randy)) self.play(randy.look_at, clipboard.get_center()) self.wait(3) class GigerenzerSession(Scene): def construct(self): # Gigerenzer intro years = TextMobject("2006-2007", font_size=72) years.to_edge(UP) image = ImageMobject("Gerd_Gigerenzer") image.set_height(4) image.flip() name = TextMobject("Gerd Gigerenzer", font_size=72) name.next_to(image, DOWN) image_group = Group(image, name) self.play(FadeIn(years, shift=0.5 * UP)) self.wait() self.play( FadeIn(image), Write(name) ) self.wait() # Seminar words title = TextMobject("Statistics Seminar", font_size=72) title.to_edge(UP) title_underline = Underline(title, buff=SMALL_BUFF) title_underline.scale(1.1) title_underline.set_stroke(LIGHT_GREY) self.play( FadeIn(title, shift=0.5 * UP), FadeOut(years, shift=0.5 * UP), ) self.play(ShowCreation(title_underline)) title.add(title_underline) # Docs doctors = ImageMobject("doctor_pair", height=2) doctors.to_corner(DL, buff=1) doctors_label = TextMobject("Practicing Gynecologists") doctors_label.match_width(doctors) doctors_label.next_to(doctors, DOWN) doctors_label.set_fill(GREY_A) self.play( FadeIn(doctors, scale=1.1), Write(doctors_label, run_time=1), image_group.scale, 0.75, image_group.to_corner, DR, ) self.wait() # Description of patient prompt = TextMobject( """ A 50-year-old woman, no symptoms, participates\\\\ in routine mammography screening. """, """She tests positive,\\\\ is alarmed, and wants to know from you whether she\\\\ has breast cancer for certain or what the chances are.\\\\ \\quad \\\\ """, """ Apart from the screening results, you know nothing\\\\ else about this woman.\\\\ """, alignment="", font_size=36, ) prompt.next_to(title_underline, DOWN, MED_LARGE_BUFF) prompt[2].shift(0.5 * UP) no_symptoms_part = prompt[0][18:28] no_symptoms_underline = Underline( no_symptoms_part, buff=0, stroke_width=3, stroke_color=YELLOW, ) clipboard = SVGMobject( "clipboard", stroke_width=0, fill_color=interpolate_color(GREY_BROWN, WHITE, 0.2) ) clipboard.next_to(prompt, DOWN) clipboard.set_width(2.5) clipboard_contents = VGroup( TextMobject("+", color=GREEN, font_size=96, stroke_width=3), TextMobject("Cancer\\\\detected", color=GREY_A), ) clipboard_contents.arrange(DOWN) clipboard_contents.set_width(0.7 * clipboard.get_width()) clipboard_contents.move_to(clipboard) clipboard_contents.shift(0.1 * DOWN) clipboard.add(clipboard_contents) self.play(FadeIn(prompt[0], lag_ratio=0.01)) self.wait() self.play(ShowCreationThenFadeOut(no_symptoms_underline)) self.wait() self.play( FadeIn(prompt[1], lag_ratio=0.01), prompt[0].set_opacity, 0.5, ) self.play( FadeIn(clipboard, shift=0.5 * UP, scale=1.1), image_group.scale, 0.75, {"about_edge": DR}, ) self.wait() self.play( FadeIn(prompt[2], lag_ratio=0.01), prompt[1].set_opacity, 0.5, clipboard.scale, 0.7, {"about_edge": DOWN}, ) self.wait() # Push prompt lower prompt.generate_target() prompt.target.set_opacity(0.8) prompt.target.replace(image_group, 0) prompt.target.scale(1.5, about_edge=RIGHT) h_line = DashedLine(FRAME_WIDTH * LEFT / 2, FRAME_WIDTH * RIGHT / 2) h_line.set_stroke(GREY_C) h_line.next_to(clipboard, UP) h_line.set_x(0) self.play( FadeOut(title, shift=UP), MoveToTarget(prompt), ShowCreation(h_line), FadeOut(image_group, shift=DR), clipboard.shift, 0.5 * LEFT, ) # Test statistics stats = VGroup( TextMobject("Prevalence: ", "1\\%"), TextMobject("Sensitivity: ", "90\\%"), TextMobject("Specificity: ", "91\\%"), ) stats.arrange(DOWN, buff=0.5, aligned_edge=LEFT) stats.to_corner(UL, buff=LARGE_BUFF) colors = [YELLOW, GREEN_B, GREY_B] for stat, color in zip(stats, colors): stat[0].set_color(color) stat[1].align_to(stats[0][1], LEFT) for stat in stats: self.play(FadeIn(stat[0], shift=0.25 * UP)) self.play(Write(stat[1])) self.wait() self.wait() # Show randy knowing the answer randy = Randolph(height=2) randy.flip() randy.next_to(h_line, UP) randy.to_edge(RIGHT) bubble = randy.get_bubble( height=2, width=2, ) bubble.shift(0.2 * LEFT) bubble.write("$\\frac{1}{11}$") self.play(FadeIn(randy)) self.play( randy.change, "thinking", ShowCreation(bubble), Write(bubble.content), ) self.play(Blink(randy)) self.wait() # Show population dot = Dot() globals()['dot'] = dot dots = VGroup(*(dot.copy() for x in range(1000))) dots.arrange_in_grid(25, 40, buff=SMALL_BUFF) dots.set_height(4) dots.to_corner(UR) dots.set_fill(GREY_D) VGroup(*random.sample(list(dots), 10)).set_fill(YELLOW) cross = Cross(dots) cross.set_stroke(RED, 30) self.play( LaggedStartMap(FadeOut, VGroup(randy, bubble, bubble.content)), ShowIncreasingSubsets(dots, run_time=2) ) self.wait() self.play(ShowCreation(cross)) self.play(FadeOut(dots), FadeOut(cross)) self.wait() self.play(LaggedStart(*( ShowCreationThenFadeOut(SurroundingRectangle( stat[1], color=stat[0].get_color() )) for stat in stats ))) # Ask question question = TextMobject( "How many women who test positive\\\\actually have breast cancer?", font_size=36 ) question.to_corner(UR) question.shift(LEFT) self.play(FadeIn(question, lag_ratio=0.1)) choices = VGroup( TextMobject("A) 9 in 10"), TextMobject("B) 8 in 10"), TextMobject("C) 1 in 10"), TextMobject("D) 1 in 100"), ) choices.arrange_in_grid(2, 2, h_buff=1.0, v_buff=0.5, aligned_edge=LEFT) choices.next_to(question, DOWN, buff=0.75) choices.set_fill(GREY_A) self.play(LaggedStart(*( FadeIn(choice, scale=1.2) for choice in choices ), lag_ratio=0.3)) self.wait() # Comment on choices a_rect = SurroundingRectangle(choices[0]) a_rect.set_color(BLUE) q_mark = TexMobject("?") q_mark.match_height(a_rect) q_mark.next_to(a_rect, LEFT) q_mark.match_color(a_rect) q_mark2 = q_mark.copy() q_mark2.move_to(doctors, UR) c_rect = SurroundingRectangle(choices[2]) c_rect.set_color(GREEN) checkmark = Checkmark().next_to(c_rect, LEFT) self.play( ShowCreation(a_rect), Write(q_mark2) ) self.play(TransformFromCopy(q_mark2, q_mark)) self.wait() # One fifth of doctors curr_doc_group = Group( doctors, clipboard, doctors_label, q_mark2, ) new_doctors = VGroup( SVGMobject("female_doctor"), SVGMobject("female_doctor"), SVGMobject("female_doctor"), SVGMobject("male_doctor"), SVGMobject("male_doctor"), ) for doc in new_doctors: doc.remove(doc[0]) doc.set_stroke(width=0) doc.set_fill(GREY_B) new_doctors.arrange_in_grid(2, 2, buff=MED_LARGE_BUFF) new_doctors[4].move_to(new_doctors[:4]) new_doctors.set_height(2.75) new_doctors.move_to(doctors, UP) marks = VGroup() for n, doc in enumerate(new_doctors): mark = Checkmark() if n == 0 else Exmark() mark.move_to(doc.get_corner(UL)) mark.shift(SMALL_BUFF * DR) marks.add(mark) new_doctors[0].set_color(GREEN) self.play( LaggedStartMap(FadeOut, curr_doc_group, scale=0.5, run_time=1), FadeIn(new_doctors, lag_ratio=0.1) ) self.play( ReplacementTransform(a_rect, c_rect), FadeOut(q_mark), Write(checkmark) ) self.wait() class OldGigerenzerMaterial(Scene): def construct(self): # Test sensitivity prompt.generate_target() prompt.target.set_opacity(0.8) prompt.target.match_height(clipboard) prompt.target.scale(0.65) prompt.target.next_to(clipboard, RIGHT, MED_LARGE_BUFF) sensitivity_words = TexMobject("90", "\\%", "\\text{ Sensitivity}") sensitivity_words.to_edge(UP) self.play( FadeIn(sensitivity_words), FadeOut(title, shift=UP), MoveToTarget(prompt), ShowCreation(h_line) ) woman = WomanIcon() women = VGroup(*[woman.copy() for x in range(100)]) women.arrange_in_grid(h_buff=2, v_buff=1) women.set_height(3) women.next_to(sensitivity_words, DOWN) women_rect = SurroundingRectangle(women, buff=0.15) women_rect.set_stroke(YELLOW, 2) with_bc_label = TextMobject( "Women\\\\with\\\\breast\\\\cancer", font_size=36, color=women_rect.get_color() ) with_bc_label.next_to(women_rect, LEFT) women.generate_target() signs = VGroup() for n, woman in enumerate(women.target): if n < 90: sign = TexMobject("+", color=GREEN) woman.set_color(GREEN) else: sign = TexMobject("-", color=RED) woman.set_color(RED) sign.match_width(woman) sign.move_to(woman.get_corner(UR), LEFT) signs.add(sign) self.play( FadeIn(women_rect), FadeIn(with_bc_label), FadeIn(women, lag_ratio=0.01), ) self.wait() self.play( MoveToTarget(women), FadeIn(signs, lag_ratio=0.01) ) self.wait() sens_group = VGroup( sensitivity_words, with_bc_label, women_rect, women, signs ) # Specificity specificity_words = TexMobject("91", "\\%", "\\text{ Specificity}") specificity_words.move_to(sensitivity_words) spec_women = women.copy() spec_women.set_fill(GREY_C) spec_rect = women_rect.copy() spec_rect.set_color(interpolate_color(GREY_BROWN, WHITE, 0.5)) wo_bc_label = TextMobject( "Women\\\\ \\emph{without} \\\\breast\\\\cancer", font_size=36, ) wo_bc_label.next_to(spec_rect, LEFT) wo_bc_label.match_color(spec_rect) spec_group = VGroup( specificity_words, wo_bc_label, spec_rect, spec_women, ) spec_group.next_to(ORIGIN, RIGHT, buff=1) spec_group.to_edge(UP) self.play( sens_group.next_to, ORIGIN, LEFT, {"buff": 2}, sens_group.to_edge, UP, FadeIn(spec_group, shift=RIGHT), ) self.wait() spec_women.generate_target() spec_signs = VGroup() for n, woman in enumerate(spec_women.target): if n < 9: sign = TexMobject("+", color=GREEN) woman.set_color(GREEN) else: sign = TexMobject("-", color=RED) woman.set_color(RED) sign.match_width(woman) sign.move_to(woman.get_corner(UR), LEFT) spec_signs.add(sign) self.play( MoveToTarget(spec_women), FadeIn(spec_signs, lag_ratio=0.01) ) self.wait() spec_group.add(spec_signs) # False negatives/False positives fnr = TexMobject( "\\leftarrow", "10", "\\%", " \\text{ false negative}", font_size=36, ) fnr.next_to(women[-1], RIGHT, buff=0.3) fpr = TexMobject( "9", "\\%", "\\text{ false positive}", "\\rightarrow", font_size=36, ) fpr.next_to(spec_women[0], LEFT, buff=0.3) self.play(FadeIn(fnr, shift=0.2 * RIGHT, scale=2)) self.play(FadeIn(fpr, shift=0.2 * LEFT, scale=2)) class AskIfItsAParadox(TeacherStudentsScene): def construct(self): # Add fact stats = VGroup( TextMobject("Sensitivity: ", "90\\%"), TextMobject("Specificity: ", "91\\%"), TextMobject("Prevalence: ", "1\\%"), ) stats.arrange(DOWN, buff=0.25, aligned_edge=LEFT) for stat, color in zip(stats, [GREEN_B, GREY_B, YELLOW]): stat[0].set_color(color) stat[1].align_to(stats[0][1], LEFT) brace = Brace(stats, UP) prob = TexMobject( "P(\\text{Cancer} \\,|\\, +) \\approx \\frac{1}{11}", tex_to_color_map={ "\\text{Cancer}": YELLOW, "+": GREEN, } ) prob.next_to(brace, UP, buff=SMALL_BUFF) fact = VGroup(stats, brace, prob) fact.to_corner(UL) box = SurroundingRectangle(fact, buff=MED_SMALL_BUFF) box.set_fill(BLACK, 0.5) box.set_stroke(WHITE, 2) fact.add_to_back(box) fact.to_edge(UP, buff=SMALL_BUFF) self.add(fact) # Commentary self.student_says( "I'm sorry, is that\\\\a paradox?", target_mode="sassy", student_index=1 ) self.change_student_modes( "angry", "sassy", "angry", added_anims=[self.teacher.change, "guilty"] ) self.wait(2) p_triangle = SVGMobject("PenroseTriangle") p_triangle.remove(p_triangle[0]) p_triangle.set_fill(opacity=0) p_triangle.set_stroke(GREY_B, 3) p_triangle.set_gloss(1) p_triangle.set_height(3) p_triangle.next_to(self.students[2].get_corner(UR), UP) p_triangle = CurvesAsSubmobjects(p_triangle[0]) self.play( self.students[0].change, "thinking", p_triangle, RemovePiCreatureBubble( self.students[1], target_mode="tease", look_at_arg=p_triangle, ), self.students[2].change, "raise_right_hand", p_triangle, self.teacher.change, "tease", p_triangle, ShowCreation(p_triangle, run_time=2, lag_ratio=0.01), ) self.wait(3) # Veridical paradox v_paradox = TextMobject("``Veridical paradox''", font_size=72) v_paradox.move_to(self.hold_up_spot, DOWN) v_paradox.to_edge(UP) v_paradox.shift(0.5 * LEFT) vp_line = Underline(v_paradox) self.play( FadeIn(v_paradox, shift=UP), self.get_student_changes(*3 * ["pondering"], look_at_arg=v_paradox), FadeOut(p_triangle, shift=UP), self.teacher.change, "raise_right_hand", v_paradox ) self.play(ShowCreation(vp_line)) self.wait() definition = TextMobject( "- Provably true\\\\", "- Seems false", alignment="", ) definition.next_to(v_paradox, DOWN, MED_LARGE_BUFF, aligned_edge=LEFT) for part in definition: self.play(FadeIn(part, shift=0.25 * RIGHT)) self.wait() self.wait(5) class GoalsOfEstimation(TeacherStudentsScene): def construct(self): # Goal goal = TextMobject("Goal: Quick estimations") goal.to_edge(UP) goal_line = Underline(goal) self.look_at(goal, added_anims=[FadeIn(goal, shift=0.5 * UP)]) self.play( ShowCreation(goal_line), self.get_student_changes( *3 * ["pondering"], look_at_arg=goal_line, ) ) self.wait() # Generators def generate_stats(prev, sens, spec, bottom=self.hold_up_spot): stats = VGroup( TextMobject("Prevalence: ", f"{prev}\\%"), TextMobject("Sensitivity: ", f"{sens}\\%"), TextMobject("Specificity: ", f"{spec}\\%"), ) stats.arrange(DOWN, buff=0.25, aligned_edge=LEFT) for stat, color in zip(stats, [YELLOW, GREEN_B, GREY_B]): stat[0].set_color(color) stat[1].align_to(stats[0][1], LEFT) rect = SurroundingRectangle(stats, buff=0.2) rect.set_fill(BLACK, 1) rect.set_stroke(GREY_B, 2) stats.add_to_back(rect) stats.move_to(bottom, DOWN) return stats def generate_answer(ans_tex): return TexMobject( "P(\\text{Cancer} \\,|\\, {+})", ans_tex, tex_to_color_map={ "\\text{Cancer}": YELLOW, "{+}": GREEN, } ) stats = [ generate_stats(1, 90, 91), generate_stats(10, 90, 91), generate_stats(0.1, 90, 91), generate_stats(1, 90, 99), ] # Question and answer self.teacher_holds_up(stats[0]) self.wait() self.student_says( generate_answer("\\approx \\frac{1}{11}"), target_mode="hooray", student_index=0, run_time=1, ) self.wait(3) stats[1].to_edge(RIGHT) self.play( FadeOut(stats[0]), self.teacher.change, "raise_left_hand", stats[1], FadeIn(stats[1], shift=0.5 * UP), RemovePiCreatureBubble(self.students[0]), ) self.play(ShowCreationThenFadeAround(stats[1][1][1])) self.change_student_modes( "pondering", "thinking", "confused", look_at_arg=stats[1] ) self.wait() self.student_says( generate_answer("\\text{ is} \\\\ \\text{a little over } 50\\%"), bubble_kwargs={"width": 4, "height": 3}, student_index=1, target_mode="speaking", run_time=1, added_anims=[ self.students[2].change, "erm", goal, self.teacher.change, "happy", self.students[1].eyes, ] ) self.wait(2) self.play( FadeOut(stats[1], 0.5 * UP), FadeIn(stats[2], 0.5 * UP), RemovePiCreatureBubble(self.students[1]), self.teacher.change, "raise_right_hand", stats[2], self.get_student_changes(*3 * ["pondering"], look_at_arg=stats[2]) ) self.play(ShowCreationThenFadeAround(stats[2][1][1])) self.wait(2) self.student_says( generate_answer("\\approx \\frac{1}{100}"), bubble_kwargs={"width": 4, "height": 3}, student_index=0, target_mode="tease", run_time=1, ) self.look_at(self.students[0].bubble.content) self.wait(2) stats[3].to_edge(RIGHT) self.play( FadeOut(stats[2], 0.5 * UP), FadeIn(stats[3], 0.5 * UP), self.teacher.change, "raise_left_hand", RemovePiCreatureBubble(self.students[0]), *(ApplyMethod(pi.look_at, stats[3]) for pi in self.pi_creatures) ) self.play(ShowCreationThenFadeAround(stats[3][1][1])) self.play( ShowCreationThenFadeAround( stats[3][3][1], surrounding_rectangle_config={"color": TEAL}, ), self.teacher.change, "tease", stats[3] ) self.change_student_modes( "pondering", "thinking", "confused", look_at_arg=self.teacher.get_bottom(), ) self.wait(2) self.student_says( generate_answer("\\text{ is} \\\\ \\text{a little below } 50\\%"), bubble_kwargs={"width": 4, "height": 3}, student_index=1, target_mode="speaking", run_time=1, added_anims=[ self.students[0].change, "erm", goal, self.students[2].change, "confused", goal, self.teacher.change, "happy", self.students[1].eyes, ] ) self.wait(2) self.change_student_modes( "thinking", "hooray", "thinking", look_at_arg=self.students[1].bubble.content, ) self.wait(3) class QuickEstimatesAndMisconceptions(Scene): def construct(self): titles = VGroup( TextMobject("Quick\\\\estimations"), TextMobject("Combating\\\\misconceptions"), ) for title, u in zip(titles, [-1, 1]): title.set_x(u * FRAME_WIDTH / 4) titles.to_edge(UP, buff=MED_SMALL_BUFF) circles = VGroup( Circle(color=BLUE), Circle(color=RED), ) circles[1].flip() circles.set_height(4) circles.set_stroke(width=3) circles.set_fill(opacity=0.5) for circle, title, u in zip(circles, titles, [-1, 1]): circle.set_x(u) title.set_color( interpolate_color(circle.get_color(), WHITE, 0.5) ) title.next_to(circle, UP) title.shift(u * RIGHT) self.play( LaggedStartMap(FadeIn, titles, shift=0.2 * UP), LaggedStartMap(DrawBorderThenFill, circles), run_time=1.5, ) self.wait() arrow = TexMobject("\\leftrightarrow", font_size=72) arrow.move_to(circles) self.play( circles[0].next_to, arrow, LEFT, circles[1].next_to, arrow, RIGHT, titles[0].shift, 0.7 * LEFT, titles[1].shift, 0.7 * RIGHT, GrowFromCenter(arrow), ) self.wait(2) self.play( FadeOut(arrow, DOWN), circles[0].shift, 2.0 * RIGHT, circles[1].shift, 2.0 * LEFT, titles[0].shift, 1.0 * RIGHT, titles[1].shift, 1.0 * LEFT, ) self.wait() titles[1].save_state() self.play( FadeOut(titles[0]), FadeOut(circles[0]), titles[1].match_x, circles[1] ) self.wait() circles[0].save_state() titles[0].save_state() circles[0].next_to(circles[1], LEFT, MED_LARGE_BUFF) titles[0].next_to(circles[0], UP) self.play( FadeIn(titles[0]), FadeIn(circles[0]), ) self.wait() bayes_factor = TextMobject("Bayes\\\\factor", font_size=72) bayes_factor.move_to(circles[0]) self.play(Write(bayes_factor)) self.wait() restoration_group = VGroup(circles[0], titles[0], titles[1]) self.add(restoration_group, bayes_factor) self.play( bayes_factor.move_to, VGroup(circles[0].saved_state, circles[1]), LaggedStartMap(Restore, restoration_group) ) self.wait() class WhatDoYouTellThem(Scene): def construct(self): text = TextMobject("What do you tell them?", font_size=72) text.set_color(BLUE) self.play(Write(text)) self.wait(5) class AccuracyImage(Scene): def construct(self): sens = 90 spec = 91 stats = VGroup( TextMobject("Sensitivity: ", f"{sens}\\%"), TextMobject("Specificity: ", f"{spec}\\%"), ) stats.arrange(DOWN, buff=0.25, aligned_edge=LEFT) for stat, color in zip(stats, [GREEN_B, GREY_B]): stat[0].set_color(color) stat[1].align_to(stats[0][1], LEFT) rect = SurroundingRectangle(stats, buff=0.2) rect.set_fill(GREY_E, 1) rect.set_stroke(GREY_B, 2) stats.add_to_back(rect) self.add(stats) class SamplePopulation10PercentPrevalence(Scene): def construct(self): # Setup test accuracy figures accuracy_figures = VGroup( TextMobject( "90\\% Sensitivity,", " 10\\% False negative rate", font_size=36 ), TextMobject( "91\\% Specificity,", " 9\\% False positive rate", font_size=36 ), ) accuracy_figures.arrange(RIGHT, buff=LARGE_BUFF) accuracy_figures.to_edge(UP) for color, text in zip([YELLOW, GREY], accuracy_figures): text.add(Underline(text, color=color, stroke_width=2)) # Show population population = VGroup(*[WomanIcon() for x in range(100)]) population.arrange_in_grid(fill_rows_first=False) population.set_height(5) population.next_to(accuracy_figures, DOWN, LARGE_BUFF) cancer_cases = population[:10] healthy_cases = population[10:] cancer_cases.set_fill(YELLOW) healthy_cases.set_fill(GREY) population.generate_target() reordered_pop = VGroup(*population) reordered_pop.shuffle() for m1, m2 in zip(reordered_pop, population.target): m1.move_to(m2) population.target[:10].next_to(accuracy_figures[0], DOWN, MED_LARGE_BUFF) population.target[10:].next_to(accuracy_figures[1], DOWN, MED_LARGE_BUFF) pop_words = TextMobject("100", " patients") wc_words = TextMobject("10", " with cancer") wo_words = TextMobject("90", " without cancer") pop_words.next_to(population, DOWN) wc_words.next_to(population.target[:10], DOWN) wo_words.next_to(population.target[10:], DOWN) for words in wc_words, wo_words: words.save_state() words[0].replace(pop_words[0], stretch=True) words[1].replace(pop_words[1], stretch=True) words.set_opacity(0) title = TextMobject("Picture a concrete population", font_size=72) title.to_edge(UP) self.add(title) self.play( FadeIn(population, lag_ratio=0.05, run_time=3), FadeIn(pop_words), ) self.wait() self.play( MoveToTarget(population, run_time=2), Restore(wc_words), Restore(wo_words), FadeOut(pop_words), title.shift, 0.25 * UP, ) self.wait() # Show test stats self.play( FadeOut(title, shift=UP), FadeIn(accuracy_figures[0], 0.5 * UP) ) # Show test results c_boxes = VGroup(*( SurroundingRectangle(icon, buff=0) for icon in cancer_cases )) h_boxes = VGroup(*( SurroundingRectangle(icon, buff=0) for icon in healthy_cases )) all_boxes = VGroup(c_boxes, h_boxes) all_boxes.set_stroke(width=3) c_boxes[:9].set_stroke(GREEN) c_boxes[9:].set_stroke(RED) h_boxes.set_stroke(RED) false_positives = healthy_cases[0:80:10] false_positive_boxes = h_boxes[0:80:10] false_positive_boxes.set_stroke(GREEN) for n, box in enumerate(c_boxes): tex = "+" if n < 9 else "-" sign = TexMobject(tex, font_size=36) sign.next_to(box, RIGHT, SMALL_BUFF) sign.match_color(box) box.add(sign) for box in false_positive_boxes: sign = TexMobject("+", font_size=36) sign.next_to(box, UP, SMALL_BUFF) sign.match_color(box) box.add(sign) for i, boxes in (0, c_boxes), (1, h_boxes): if i == 1: self.play(FadeIn(accuracy_figures[1])) self.play(ShowCreationThenFadeOut(SurroundingRectangle( accuracy_figures[i][i], buff=SMALL_BUFF, stroke_color=GREEN, ))) self.wait() self.play(ShowIncreasingSubsets(boxes)) self.wait() for icon, box in zip(cancer_cases, c_boxes): icon.add(box) for icon, box in zip(healthy_cases, h_boxes): icon.add(box) self.remove(all_boxes) self.add(population) # Filter down to positive cases new_wc_words = TextMobject("9 ", "with cancer") new_wo_words = TextMobject("8 ", "without cancer") for nw, w in (new_wc_words, wc_words), (new_wo_words, wo_words): nw[0].set_color(GREEN) nw.move_to(w) wc_words.unlock_triangulation() self.play( cancer_cases[:9].move_to, cancer_cases, FadeOut(cancer_cases[9:]), ReplacementTransform(wc_words, new_wc_words), ) self.wait() wo_words.unlock_triangulation() self.play( false_positives.move_to, healthy_cases, FadeOut(VGroup(*( case for case in healthy_cases if case not in false_positives ))), ReplacementTransform(wo_words, new_wo_words), ) self.wait() # Rearrange true positives false_positives.generate_target() false_positives.target.shift(DOWN) true_positives = cancer_cases[:9] true_positives.generate_target() for case in true_positives.target: box = case[-1] sign = box[-1] sign.next_to(case[:-1], UP, SMALL_BUFF) true_positives.target.arrange( RIGHT, buff=get_norm(false_positives[0].get_right() - false_positives[1].get_left()), ) true_positives.target.match_y(false_positives.target) true_positives.target.match_x(new_wc_words) self.play( MoveToTarget(true_positives), MoveToTarget(false_positives), new_wc_words.next_to, true_positives.target, DOWN, new_wo_words.next_to, false_positives.target, DOWN, ) self.wait() # Show final fraction answer = TexMobject( "{9 \\over 9 + 8} \\approx 0.53", font_size=72, )[0] answer.next_to(accuracy_figures, DOWN, LARGE_BUFF) nine1 = new_wc_words[0].copy() nine2 = new_wc_words[0].copy() eight = new_wo_words[0].copy() self.play( nine1.replace, answer[0], nine2.replace, answer[2], eight.replace, answer[4], Write(answer[1:5:2]), ) self.wait() self.play(Write(answer[5:])) self.wait() class NinePercentOfNinety(Scene): def construct(self): mob = TexMobject("(0.09)(90) = 8.1", tex_to_color_map={"8.1": GREEN}) mob.scale(2) self.add(mob) class MoreExamples(TeacherStudentsScene): def construct(self): self.teacher_says( "More examples!", target_mode="hooray", added_anims=[self.get_student_changes("tired", "erm", "happy", run_time=2)] ) self.wait(3) class SamplePopulationOneInThousandPrevalence(Scene): def construct(self): # Add prevalence title titles = VGroup(*( TextMobject( f"What if prevalence is {n} in {k}?", tex_to_color_map={ n: YELLOW, k: GREY_B, } ) for n, k in [("{1}", "{1,000}"), ("{10}", "{10,000}")] )) titles.to_edge(UP) self.add(titles[0]) # Show population dots1k, dots10k = [ VGroup(*(Dot() for x in range(n))).set_fill(GREY_D) for n in [1000, 10000] ] dots1k[:1].set_fill(YELLOW) dots10k[:10].set_fill(YELLOW) for dots, n, m in [(dots1k, 20, 50), (dots10k, 100, 100)]: sorter = VGroup(*dots) sorter.shuffle() sorter.arrange_in_grid(n, m, buff=SMALL_BUFF) sorter.set_width(FRAME_WIDTH - 1) if sorter.get_height() > 6: sorter.set_height(6) sorter.to_edge(DOWN) self.play(FadeIn(dots1k, lag_ratio=0.05, run_time=2)) self.wait() self.play( Transform(dots1k, dots10k[0::10]), ) self.play( FadeIn(dots10k), ReplacementTransform(titles[0][0::2], titles[1][0::2]), FadeOut(titles[0][1::2], 0.5 * UP), FadeIn(titles[1][1::2], 0.5 * UP), ) self.remove(dots1k) # Split the group cancer_cases = dots10k[:10] cancer_cases.generate_target() cancer_cases.target.arrange_in_grid( buff=cancer_cases[0].get_width() / 2, ) cancer_cases.target.set_height(2) cancer_cases.target.set_y(0) cancer_cases.target.to_edge(LEFT, buff=LARGE_BUFF) non_cancer_cases = dots10k[10:] non_cancer_cases.generate_target() non_cancer_cases.target.to_edge(RIGHT) c_count = titles[1][1].copy() c_count.generate_target() c_count.target.next_to(cancer_cases.target, UP, MED_LARGE_BUFF) nc_count = Integer(9990) nc_count.set_color(GREY_B) nc_count.set_opacity(0) nc_count.move_to(titles[1][3]) self.play( MoveToTarget(cancer_cases), MoveToTarget(non_cancer_cases), MoveToTarget(c_count), nc_count.set_opacity, 1, nc_count.next_to, non_cancer_cases.target, UP, FadeOut(titles[1]), ) self.wait() # Show test results tp_cases = cancer_cases[:9] fp_cases = VGroup(*random.sample(list(non_cancer_cases), 900)) for case in it.chain(tp_cases, fp_cases): box = SurroundingRectangle( case, buff=0.1 * case.get_width() ) box.set_stroke(GREEN, 3) case.box = box tn_cases = VGroup(*( case for case in non_cancer_cases if case not in fp_cases )) tp_label = TextMobject("$9$ True Positives") tp_label.set_color(GREEN) tp_label.move_to(c_count) tp_label.shift_onto_screen() fp_label = TextMobject("$\\sim 900$ False Positives") fp_label.set_color(GREEN_D) fp_label.move_to(nc_count) self.play( FadeOut(c_count), FadeIn(tp_label), LaggedStart(*( ShowCreation(case.box) for case in tp_cases )), cancer_cases[9].set_opacity, 0.25 ) self.wait() self.play( FadeOut(nc_count), FadeIn(fp_label), LaggedStart(*( ShowCreation(case.box) for case in fp_cases ), lag_ratio=0.001), tn_cases.set_opacity, 0.25, run_time=2, ) self.wait() for case in it.chain(tp_cases, fp_cases): case.add(case.box) # Organize false positives center = fp_cases.get_center() fp_cases.sort(lambda p: get_norm(p - center)) fp_cases.generate_target() fp_cases.target.arrange_in_grid( buff=fp_cases[0].get_width() / 4, ) fp_cases.target.set_height(6) fp_cases.target.to_corner(DR) new_center = fp_cases.target.get_center() fp_cases.target.sort(lambda p: get_norm(p - new_center)) self.play( MoveToTarget(fp_cases, run_time=4), FadeOut(tn_cases, run_time=1), FadeOut(cancer_cases[9]), ) self.wait() # Final fraction final_frac = TexMobject( "{{9} \\over {9} + {900}} \\approx 0.01", tex_to_color_map={ "{9}": GREEN, "{900}": GREEN_D, } ) final_frac.scale(1.5) final_frac.next_to(tp_cases, DOWN, LARGE_BUFF) final_frac.to_edge(LEFT, LARGE_BUFF) self.play(FadeIn(final_frac, lag_ratio=0.2)) self.wait() class AltShowUpdatingPrior(Scene): def construct(self): N = 100 post_denom = 11 N_str = "{:,}".format(N) # Show prior woman = WomanIcon() population = VGroup(*[woman.copy() for x in range(N)]) population.arrange_in_grid() population.set_fill(GREY) population[0].set_fill(YELLOW) population.set_height(5) prior_prob = TextMobject("1", " in ", N_str) prior_prob.set_color_by_tex("1", YELLOW) prior_prob.set_color_by_tex(N_str, GREY_B) prior_prob.next_to(population, UP, MED_LARGE_BUFF) prior_brace = Brace(prior_prob, UP, buff=SMALL_BUFF) prior_words = prior_brace.get_text("Prior") prior_words.add_updater(lambda m: m.next_to(prior_brace, UP, SMALL_BUFF)) thousand_part = prior_prob.get_part_by_tex(N_str) pop_count = Integer(N, edge_to_fix=UL) pop_count.replace(thousand_part) pop_count.match_color(thousand_part) pop_count.set_value(0) prior_prob.replace_submobject(2, pop_count) VGroup(population, prior_prob, prior_brace, prior_words).to_corner(UL) self.add(prior_prob) # Before word before_words = TextMobject( "Probability of having the disease\\\\ ", "\\emph{before} taking a test" ) before_words.set_color(BLUE_B) before_words.next_to(prior_words, RIGHT, buff=3, aligned_edge=UP) before_arrow1 = Arrow(before_words[0][0].get_left(), prior_prob.get_right() + MED_SMALL_BUFF * RIGHT) before_arrow2 = Arrow(before_words[0][0].get_left(), prior_words.get_right()) before_arrow1.match_color(before_words) before_arrow2.match_color(before_words) self.play( FadeIn(before_words, lag_ratio=0.1), GrowArrow(before_arrow1), ShowIncreasingSubsets(population, run_time=2), ChangeDecimalToValue(pop_count, len(population), run_time=2), ) self.wait() self.play( GrowFromCenter(prior_brace), FadeIn(prior_words, shift=0.5 * UP), ReplacementTransform(before_arrow1, before_arrow2) ) self.wait() # Update arrow update_arrow = Arrow(2 * LEFT, 2 * RIGHT) update_arrow.set_thickness(0.1) update_arrow.center() update_arrow.match_y(pop_count) update_words = TextMobject("See positive test", tex_to_color_map={"positive": GREEN}) update_words.next_to(update_arrow, UP, SMALL_BUFF) low_update_words = TextMobject("Update probability", font_size=36) low_update_words.next_to(update_arrow, DOWN, MED_SMALL_BUFF) # Posterior post_pop = population[:post_denom].copy() post_pop.arrange_in_grid( buff=get_norm(population[1].get_left() - population[0].get_right()) ) post_pop.match_height(population) post_pop.next_to( update_arrow, RIGHT, buff=abs(population.get_right()[0] - update_arrow.get_left()[0]) ) post_pop.align_to(population, UP) def give_pop_plusses(pop): for icon in pop: plus = TexMobject("+") plus.set_color(GREEN) plus.set_width(icon.get_width() / 2) plus.move_to(icon.get_corner(UR)) icon.add(plus) give_pop_plusses(post_pop) post_prob = prior_prob.copy() post_prob[2].set_value(post_denom) post_prob.next_to(post_pop, UP, buff=MED_LARGE_BUFF) roughly = TextMobject("(roughly)", font_size=24) roughly.next_to(post_prob, RIGHT, buff=0.2) post_prob[2].set_value(0) self.play( FadeIn(update_words, lag_ratio=0.2), FadeOut(before_words), ReplacementTransform(before_arrow2, update_arrow, path_arc=30 * DEGREES), FadeIn(low_update_words, lag_ratio=0.2), ) self.add(post_prob) self.play( ShowIncreasingSubsets(post_pop, run_time=1), ChangeDecimalToValue(post_prob[2], post_denom, run_time=1), FadeIn(roughly), ) self.wait() post_brace = Brace(post_prob, UP, buff=SMALL_BUFF) post_words = post_brace.get_text("Posterior") post_words.add_updater(lambda m: m.next_to(post_brace, UP, SMALL_BUFF)) post_words.update(0) self.play( GrowFromCenter(post_brace), FadeIn(post_words, shift=0.5 * UP) ) self.wait() post_group = VGroup( update_arrow, update_words, post_pop, post_prob, post_brace, post_words, ) post_group.save_state() return # Change prior and posterior pop100, pop11, pop10, pop2 = pops = [ VGroup(*[woman.copy() for x in range(n)]) for n in [100, 11, 10, 2] ] for pop in pops: pop.arrange_in_grid() pop.set_fill(GREY) pop[0].set_fill(YELLOW) pop.scale( post_pop[0].get_height() / pop[0].get_height() ) pop100.replace(population) pop11.move_to(post_pop, UP) pop11.shift(0.2 * LEFT) pop10.move_to(population, UP) pop10.shift(0.4 * LEFT) pop2.move_to(post_pop, UP) pop2.shift(0.3 * LEFT) give_pop_plusses(pop11) give_pop_plusses(pop2) def replace_population_anims(old_pop, new_pop, count, brace): count.set_value(len(new_pop)) brace.generate_target() brace.target.match_width( Line(brace.get_left(), count.get_right()), about_edge=LEFT, stretch=True, ) count.set_value(len(old_pop)) return [ FadeOut(old_pop, lag_ratio=0.1), ShowIncreasingSubsets(new_pop), ChangeDecimalToValue(count, len(new_pop)), MoveToTarget(brace) ] self.play( *replace_population_anims(population, pop100, prior_prob[2], prior_brace) ) self.play( *replace_population_anims(post_pop, pop11, post_prob[2], post_brace), ) self.wait(2) self.play( *replace_population_anims(pop100, pop10, prior_prob[2], prior_brace), prior_words.shift, 0.1 * LEFT ) self.play( *replace_population_anims(pop11, pop2, post_prob[2], post_brace) ) self.wait(2) class ContrastThreeContexts(Scene): def construct(self): # Background bg_rect = FullScreenFadeRectangle() bg_rect.set_fill(GREY_E, 1) self.add(bg_rect) # Scene templates screens = VGroup(*( ScreenRectangle() for x in range(3) )) screens.set_stroke(WHITE, 2) screens.set_fill(BLACK, 1) screens.arrange(DOWN, buff=LARGE_BUFF) screens.set_height(FRAME_HEIGHT - 1) screens.next_to(ORIGIN, RIGHT) self.add(screens) # Prevalence values dots_template = VGroup(*(Dot() for x in range(1000))) dots_template.arrange_in_grid(20, 50, buff=SMALL_BUFF) dots_template.set_fill(GREY_B) dot_groups = VGroup() for screen, n in zip(screens, [1, 10, 100]): dots = dots_template.copy() dots.match_width(screen) dots.scale(0.9) dots.move_to(screen, DOWN) dots.shift(0.1 * UP) for dot in random.sample(list(dots), n): dot.set_fill(YELLOW) dot_groups.add(dots) prevalence_labels = VGroup( TextMobject("Prevalence: ", "0.1\\%"), TextMobject("Prevalence: ", "1\\%"), TextMobject("Prevalence: ", "10\\%"), ) for label, dots in zip(prevalence_labels, dot_groups): label.scale(0.75) label[1].set_color(YELLOW) label.next_to(dots, UP, buff=0.15) self.add(prevalence_labels) self.add(dot_groups) # Words words = VGroup( TextMobject("Same", " test"), TextMobject("Same", " accuracy"), TextMobject("Same", " result"), ) words.scale(1.5) words.arrange(DOWN, aligned_edge=LEFT, buff=MED_LARGE_BUFF) words.to_edge(LEFT) words.set_stroke(BLACK, 3, background=True) for word in words[:2]: self.add(word) self.wait() # Show receiving results def get_positive_result(): woman = WomanIcon() clipboard = SVGMobject( "clipboard", stroke_width=0, fill_color=interpolate_color(GREY_BROWN, WHITE, 0.2) ) clipboard.set_width(1) clipboard.next_to(woman, LEFT) content = TextMobject("+\\\\", "Cancer\\\\detected") content[0].scale(2, about_edge=DOWN) content[0].set_color(GREEN) content[0].set_stroke(GREEN, 3) content[0].shift(SMALL_BUFF * UP) content.set_width(0.7 * clipboard.get_width()) content.move_to(clipboard) content.shift(SMALL_BUFF * DOWN) clipboard.add(content) result = VGroup(woman, clipboard) result.scale(0.7) return result globals()['get_positive_result'] = get_positive_result positive_results = VGroup(*( get_positive_result() for screen in screens )) positive_results.generate_target() positive_results.set_opacity(0) for screen, result in zip(screens, positive_results.target): result.set_height(screen.get_height() * 0.5) result.next_to(screen, RIGHT, aligned_edge=DOWN) result.shift(0.25 * UP) self.add(words[2]) self.play(MoveToTarget(positive_results)) self.wait() # Different results probs = VGroup(*( Integer(0, unit="\\%").next_to(result, UP) for result in positive_results )) probs.set_fill(GREEN) self.play( ChangeDecimalToValue(probs[0], 1), ChangeDecimalToValue(probs[1], 9), ChangeDecimalToValue(probs[2], 53), UpdateFromAlphaFunc( Mobject(), lambda m, a, probs=probs: probs.set_opacity(a), remover=True, ) ) self.wait() # What test accuracy does ta_words = VGroup( TextMobject("Test", " accuracy"), TextMobject("\\emph{alone} ", "does not"), TextMobject("determine", "."), TextMobject("\\,", "your chances"), ) ta_words[2][1].set_opacity(0) ta_words[1].set_color_by_tex("not", RED) up_words = VGroup( TextMobject("Test", " accuracy"), TextMobject("determine", "s"), TextMobject("how", " your chances"), TextMobject("are", " \\emph{updated}"), ) up_words[3][1].set_color(BLUE) up_words[3].shift(SMALL_BUFF * UP) up_words[0].shift(SMALL_BUFF * DOWN) for group in ta_words, up_words: group.arrange(DOWN, buff=0.3, aligned_edge=LEFT) group.scale(1.5) group.to_edge(LEFT) group.set_stroke(BLACK, 3, background=True) words[0][1].unlock_triangulation() self.play( FadeOut(words[0][0]), FadeOut(words[1][0]), FadeOut(words[2]), ReplacementTransform(words[0][1], ta_words[0][0]), ReplacementTransform(words[1][1], ta_words[0][1]), LaggedStartMap(FadeIn, ta_words[1:]), ) self.add(ta_words) self.wait() ta_words.unlock_triangulation() self.play( ReplacementTransform(ta_words[0], up_words[0]), ReplacementTransform(ta_words[2], up_words[1]), FadeIn(up_words[2][0], scale=2), ReplacementTransform(ta_words[3][1], up_words[2][1]), FadeOut(ta_words[1], scale=0.5), ) self.play(Write(up_words[3])) self.wait() underlines = VGroup() for word in up_words: line = Underline(word) line.set_y(word[0][0].get_bottom()[1]) line.shift(SMALL_BUFF * DOWN) underlines.add(line) underlines.set_stroke(BLUE, 3) shifted_underlines = underlines.copy().shift(SMALL_BUFF * DOWN) shifted_underlines.set_stroke(BLUE_E, 3) underlines.add(*shifted_underlines) self.add(underlines, up_words) self.play(ShowCreation( underlines, run_time=2, rate_func=double_smooth, )) self.wait() # Show updates arrows = VGroup(*( Arrow( pl.get_corner(UR), prob.get_corner(UL), buff=0.1, path_arc=-45 * DEGREES, ) for pl, prob in zip(prevalence_labels, probs) )) arrows.set_color(RED) self.play(LaggedStartMap( GrowArrow, arrows, lag_ratio=0.5, path_arc=-45 * DEGREES, )) self.wait() class BayesFactor(Scene): def construct(self): # Test sensitivity woman = WomanIcon() bc_pop = VGroup(*[woman.copy() for x in range(100)]) bc_pop.arrange_in_grid(h_buff=1.5, v_buff=1) bc_pop.set_height(4) bc_pop.next_to(ORIGIN, LEFT, MED_LARGE_BUFF) bc_rect = SurroundingRectangle(bc_pop, buff=0.15) bc_rect.set_stroke(YELLOW, 2) with_bc_label = TextMobject( "Patients with\\\\breast cancer", font_size=36, color=bc_rect.get_color() ) with_bc_label.next_to(bc_rect, UP) bc_pop.generate_target() bc_signs = VGroup() for n, icon in enumerate(bc_pop.target): if n < 90: sign = TexMobject("+", color=GREEN) icon.set_color(GREEN) else: sign = TexMobject("-", color=RED) icon.set_color(RED) sign.match_width(icon) sign.move_to(icon.get_corner(UR), LEFT) bc_signs.add(sign) sens_brace = Brace(bc_pop[:90], LEFT, buff=MED_SMALL_BUFF) sens_word = sens_brace.get_text("90\\% Sens.") sens_word.set_color(GREEN) fnr_brace = Brace(bc_pop[90:], LEFT, buff=MED_SMALL_BUFF) fnr_word = fnr_brace.get_text("10\\% FNR") fnr_word.set_color(RED_E) bc_group = VGroup( with_bc_label, bc_rect, bc_pop, bc_signs, sens_brace, sens_word, fnr_brace, fnr_word, ) # Specificity (too much copy paste) nc_pop = bc_pop.copy() nc_pop.next_to(ORIGIN, RIGHT, MED_LARGE_BUFF) nc_rect = SurroundingRectangle(nc_pop, buff=0.15) nc_rect.set_stroke(GREY_B, 2) without_bc_label = TextMobject( "Patients without\\\\breast cancer", font_size=36, color=nc_rect.get_color() ) without_bc_label.next_to(nc_rect, UP) nc_pop.generate_target() nc_signs = VGroup() for n, icon in enumerate(nc_pop.target): if 0 < n < 10: sign = TexMobject("+", color=GREEN) icon.set_color(GREEN) else: sign = TexMobject("-", color=RED) icon.set_color(RED) sign.match_width(icon) sign.move_to(icon.get_corner(UR), LEFT) nc_signs.add(sign) spec_brace = Brace(nc_pop[10:], RIGHT, buff=MED_SMALL_BUFF) spec_word = spec_brace.get_text("91\\% Spec.") spec_word.set_color(RED) fpr_brace = Brace(nc_pop[:10], RIGHT, buff=MED_SMALL_BUFF) fpr_word = fpr_brace.get_text("9\\% FPR") fpr_word.set_color(GREEN_D) nc_group = VGroup( without_bc_label, nc_rect, nc_pop, nc_signs, spec_brace, spec_word, fpr_brace, fpr_word, ) # Draw groups self.play(LaggedStart( FadeIn(with_bc_label), ShowCreation(bc_rect), ShowIncreasingSubsets(bc_pop), FadeIn(without_bc_label), ShowCreation(nc_rect), ShowIncreasingSubsets(nc_pop), )) self.play(LaggedStart( MoveToTarget(bc_pop), FadeIn(bc_signs, lag_ratio=0.02), GrowFromCenter(sens_brace), FadeIn(sens_word, shift=0.2 * LEFT), GrowFromCenter(fnr_brace), FadeIn(fnr_word, shift=0.2 * LEFT), )) self.play(LaggedStart( MoveToTarget(nc_pop), FadeIn(nc_signs, lag_ratio=0.02), GrowFromCenter(spec_brace), FadeIn(spec_word, shift=0.2 * RIGHT), GrowFromCenter(fpr_brace), FadeIn(fpr_word, shift=0.2 * RIGHT), )) self.wait() groups = VGroup(bc_group, nc_group) # Highlight relevant parts fade_rects = VGroup(*(BackgroundRectangle(group) for group in groups)) fade_rects.set_fill(BLACK, 0.8) self.play(FadeIn(fade_rects[1])) self.play(LaggedStart(*( ShowCreationThenFadeOut(Underline(word)) for word in [sens_word, fnr_word] ), lag_ratio=0.4)) self.play( FadeIn(fade_rects[0]), FadeOut(fade_rects[1]), ) self.play(LaggedStart(*( ShowCreationThenFadeOut(Underline(word)) for word in [spec_word, fpr_word] ), lag_ratio=0.4)) self.wait() self.play(FadeOut(fade_rects[0])) # None of these are your answer title = TexMobject( "\\text{None of these tell you }", "P(\\text{Cancer} \\,|\\, +)", tex_to_color_map={ "\\text{Cancer}": YELLOW, "+": GREEN, }, font_size=72, ) title.to_edge(UP) title_underline = Underline(title) self.play( FadeIn(title, lag_ratio=0.1), groups.to_edge, DOWN, ) self.play(ShowCreation(title_underline)) self.wait() title.add(title_underline) # Ask about update strength question = TextMobject("How strongly\\\\does it update?") question.set_height(1) question.to_corner(UL) question.save_state() question.replace(title, 1) question.set_opacity(0) self.play( Restore(question), title.replace, question.saved_state, 0, title.set_opacity, 0, ) self.remove(title) self.wait() # Write Bayes factor frac = VGroup( sens_word.copy(), TexMobject("\\qquad \\over \\qquad"), fpr_word.copy(), ) frac[1].match_width(frac[0], stretch=True) frac.arrange(DOWN, SMALL_BUFF) frac.next_to(question, RIGHT, LARGE_BUFF) mid_rhs = TexMobject( "= {P(+ \\,|\\, \\text{Cancer}) \\over P(+ \\,|\\, \\text{No cancer})}", tex_to_color_map={ "+": GREEN, "\\text{Cancer}": YELLOW, "\\text{No cancer}": GREY_B, } ) mid_rhs.next_to(frac, RIGHT) rhs = TexMobject("= 10", font_size=72) rhs.next_to(mid_rhs, RIGHT) for part in frac[0::2]: part.save_state() frac[0].replace(sens_word) frac[2].replace(fpr_word) s_rect = SurroundingRectangle(frac[0]) self.play(ShowCreation(s_rect)) self.play( Restore(frac[0]), s_rect.move_to, frac[0].saved_state, s_rect.set_opacity, 0, Write(frac[1]), Restore(frac[2]), ) self.wait(2) self.play(Write(mid_rhs)) self.wait() self.play(Write(rhs)) self.wait() # Name the Bayes factor bf_name = TextMobject("Bayes\\\\Factor ", font_size=72) equals = TexMobject("=", font_size=72).next_to(frac, LEFT) bf_name.next_to(equals, LEFT) lr_name = TextMobject("Likelihood\\\\ratio") lr_name.next_to(equals, LEFT) self.play( FadeOut(question, UP), FadeIn(bf_name, UP), FadeIn(equals, UP), ) self.wait() self.play( FadeOut(bf_name, UP), FadeIn(lr_name, UP), ) self.wait() self.play( FadeIn(bf_name, DOWN), FadeOut(lr_name, DOWN), ) self.wait(5) class RuleOfThumb(Scene): def construct(self): tex_to_color_map = { "+": GREEN, "\\text{Cancer}": YELLOW, "\\text{No cancer}": GREY_B, "=": WHITE, "\\over": WHITE, "{90\\%": GREEN, "9\\%}": GREEN, "1\\%}": TEAL, "10": WHITE, } bf_computation = TexMobject( """ { P(+ \\, | \\, \\text{Cancer}) \\over P(+ \\, | \\, \\text{No cancer}) } = {90\\% \\over 9\\%} = 10 """, tex_to_color_map=tex_to_color_map ) bf_computation.to_edge(LEFT) self.add(bf_computation) class ProbabilityVsOdds(Scene): def construct(self): # Show titles and division titles = VGroup( TextMobject("Probability"), TextMobject("Odds"), ) for title, u in zip(titles, [-1, 1]): title.scale(1.5) title.to_edge(UP) title.set_x(FRAME_WIDTH * u / 4) h_line = Line(LEFT, RIGHT) h_line.set_width(FRAME_WIDTH) h_line.next_to(titles, DOWN, SMALL_BUFF) h_line.set_x(0) v_line = Line(UP, DOWN) v_line.set_height(FRAME_HEIGHT) v_line.center() VGroup(h_line, v_line).set_stroke(GREY, 2) titles.save_state() for title in titles: title.set_x(0) titles[1].set_opacity(0) self.add(titles) self.wait() self.play( Restore(titles), ShowCreation(v_line), ) self.play(ShowCreation(h_line)) self.wait() # Function definitions def get_people_row(n_all, n_pos=1): icon = SVGMobject("person") icon.set_fill(GREY) icon.set_stroke(WHITE, 1) icon.set_height(1) people = VGroup(*(icon.copy() for x in range(n_all))) people.arrange(RIGHT, buff=SMALL_BUFF) people[:n_pos].set_fill(YELLOW) return people def get_prob_counts(people, n_pos, n_all): pos_brace = Brace(people[:n_pos], UP, buff=SMALL_BUFF) all_brace = Brace(people, DOWN, buff=SMALL_BUFF) pos_label = Integer(n_pos, color=YELLOW) all_label = Integer(n_all) pos_label.next_to(pos_brace, UP) all_label.next_to(all_brace, DOWN) return VGroup( VGroup(pos_brace, pos_label), VGroup(all_brace, all_label), ) def get_odds_counts(people, n_pos, n_neg): pos_brace = Brace(people[:n_pos], UP, buff=SMALL_BUFF) neg_brace = Brace(people[n_pos:], UP, buff=SMALL_BUFF) pos_label = Integer(n_pos, color=YELLOW) neg_label = Integer(n_neg, color=GREY_B) pos_label.next_to(pos_brace, UP) neg_label.next_to(neg_brace, UP) return VGroup( VGroup(pos_brace, pos_label), VGroup(neg_brace, neg_label), ) def get_prob_label(n_pos, n_all): result = VGroup( Integer(n_pos, color=YELLOW), TexMobject("/"), Integer(n_all), ) result.scale(1.5) result.arrange(RIGHT, buff=0.1) return result def get_odds_label(n_pos, n_neg): result = VGroup( Integer(n_pos, color=YELLOW), TexMobject(":"), Integer(n_neg, color=GREY_B), ) result.scale(1.5) result.arrange(RIGHT, buff=0.2) return result # Show probability people = get_people_row(5) people.match_x(titles[0]) people.shift(DOWN) prob_counts = get_prob_counts(people, 1, 5) prob = get_prob_label(1, 5) prob.next_to(people, UP, MED_LARGE_BUFF) self.play( FadeIn(people, lag_ratio=0.3), Write(prob) ) self.wait() self.play( prob.shift, 1.5 * UP, TransformFromCopy(prob[0], prob_counts[0][1]), GrowFromCenter(prob_counts[0][0]), ) self.play( TransformFromCopy(prob[2], prob_counts[1][1]), GrowFromCenter(prob_counts[1][0]), ) self.wait() # Transition to odds right_people = people.copy() right_people.match_x(titles[1]) odds = get_odds_label(1, 4) odds.match_x(right_people) odds.match_y(prob) arrow = Arrow(prob.get_right(), odds.get_left(), buff=1) arrow.set_thickness(0.1) odds_count = get_odds_counts(right_people, 1, 4) self.play( TransformFromCopy(people, right_people), TransformFromCopy( odds.copy().replace(prob).set_opacity(0), odds ), GrowArrow(arrow), ) self.wait() self.play( TransformFromCopy(odds[0], odds_count[0][1]), GrowFromCenter(odds_count[0][0]), ) self.play( TransformFromCopy(odds[2], odds_count[1][1]), GrowFromCenter(odds_count[1][0]), ) self.wait() self.play(ShowCreationThenFadeAround(odds[1])) self.wait() # Other examples people.add(prob_counts) right_people.add(odds_count) def get_example(n_pos, n_all): new_prob = TexMobject( f"{int(100 * n_pos / n_all)}\\%", font_size=72 ) new_odds = get_odds_label(n_pos, n_all - n_pos) new_odds.next_to(new_prob, RIGHT, buff=1.5) arrow = Arrow( new_prob.get_right(), new_odds.get_left(), thickness=0.05 ) arrow.set_fill(GREY_A) example = VGroup(new_prob, arrow, new_odds) example.scale(0.5) return example def generate_example_movement(prob, arrow, odds, example): prob.unlock_triangulation() mover = VGroup(prob, arrow.copy(), odds) return ReplacementTransform(mover, example) def transition_to_new_example(n_pos, n_all, example, scene=self, people=people, right_people=right_people, prob=prob, odds=odds, arrow=arrow ): new_people = get_people_row(n_all, n_pos) new_people.set_x(-FRAME_WIDTH / 4) new_right_people = new_people.copy() new_right_people.set_x(FRAME_WIDTH / 4) new_prob = TexMobject( f"{int(100 * n_pos / n_all)}\\%", font_size=72 ) new_odds = get_odds_label(n_pos, n_all - n_pos) new_prob.move_to(prob) new_odds.move_to(odds) scene.play( generate_example_movement(prob, arrow, odds, example), FadeOut(people), FadeOut(right_people), FadeIn(new_prob), FadeIn(new_odds), FadeIn(new_people), FadeIn(new_right_people), ) return { "people": new_people, "right_people": new_right_people, "prob": new_prob, "odds": new_odds, } examples = VGroup(*( get_example(n, k) for n, k in [(1, 10), (1, 5), (1, 2), (4, 5), (9, 10)] )) examples.arrange(UP) examples.to_edge(DOWN) kw = {} for n, k, ei in (1, 2, 1), (1, 10, 2), (4, 5, 0), (9, 10, 3): kw = transition_to_new_example( n, k, examples[ei], **kw ) self.wait(2) self.remove(arrow) self.play( generate_example_movement( kw["prob"], arrow, kw["odds"], examples[4], ), FadeOut(kw["people"]), FadeOut(kw["right_people"]), ) # Show ranges left_range = UnitInterval((0, 1, 0.1), width=5) left_range.set_y(1.5) left_range.match_x(titles[0]) right_range = NumberLine((0, 5), width=5, include_tip=True) right_range.match_y(left_range) right_range.match_x(titles[1]) dots = TexMobject("\\dots") dots.next_to(right_range, RIGHT) right_range.add(dots) right_range.add(*( right_range.get_tick( 1 / n, right_range.tick_size * (1 / n)**0.5, ).set_opacity((1 / n)**0.5) for n in range(2, 100) )) left_range.add_numbers([0, 0.2, 0.5, 0.8, 1]) right_range.add_numbers() left_ticker = ArrowTip(angle=-90 * DEGREES) left_ticker.set_fill(BLUE) left_ticker.move_to(left_range.n2p(0.5), DOWN) right_ticker = left_ticker.copy() right_ticker.set_color(RED) right_ticker.move_to(right_range.n2p(1), DOWN) self.play( Write(left_range), Write(right_range), run_time=1 ) self.play( FadeIn(left_ticker, shift=DOWN), FadeIn(right_ticker, shift=DOWN), ) rect = SurroundingRectangle(examples[0]) rect.set_opacity(0) prob = ValueTracker(0.5) left_ticker.add_updater( lambda m: m.move_to(left_range.n2p(prob.get_value()), DOWN) ) right_ticker.add_updater( lambda m: m.move_to(right_range.n2p( prob.get_value() / (1 - prob.get_value()) ), DOWN) ) for i, p in enumerate([0.1, 0.2, 0.5, 0.8, 0.9]): self.play( prob.set_value, p, rect.become, SurroundingRectangle(examples[i]), ) self.wait() self.play( FadeOut(rect), ApplyMethod(prob.set_value, 0.5, run_time=4), ) self.wait() class OddsComments(Scene): def construct(self): morty = Mortimer() morty.to_corner(DR) randy = Randolph() randy.next_to(morty, LEFT, buff=2) self.play(FadeIn(morty)) self.play( PiCreatureSays( morty, "The chances are\\\\1 to 1", run_time=1 ), FadeIn(randy) ) self.play(Blink(morty)) self.play( PiCreatureSays(randy, "The chances are\\\\2 to 1", run_time=1), RemovePiCreatureBubble(morty, target_mode="happy") ) self.play(Blink(morty)) self.play(Blink(randy)) self.wait() class NewSnazzyBayesRuleSteps(Scene): def construct(self): # Add title title = TextMobject( "Bayes' rule, the snazzy way", font_size=72 ) title.to_edge(UP) title.set_stroke(BLACK, 2, background=True) underline = Underline(title) underline.scale(1.2) underline.shift(0.2 * UP) underline.set_stroke(GREY, 3) self.add(underline, title) # Completely accurate accurate_words = TextMobject( *"Completely accurate\\\\ Not even approximating things".split(" "), font_size=72, arg_separator=" " ) accurate_words.set_color(BLUE) for word in accurate_words: self.add(word) self.wait(0.05 * len(word)) # Population population = VGroup(*( WomanIcon() for x in range(100) )) population.arrange_in_grid(h_buff=1, v_buff=0.5, fill_rows_first=False) population.set_height(5) population.to_corner(DR) population[0].set_color(YELLOW) # Step labels step_labels = VGroup( TextMobject("Step 1)"), TextMobject("Step 2)"), TextMobject("Step 3)"), ) step_labels.arrange(DOWN, buff=1.5, aligned_edge=LEFT) step_labels.next_to(title, DOWN, MED_LARGE_BUFF) step_labels.to_edge(LEFT) step1, step2, step3 = steps = VGroup( TextMobject("Express the prior with odds"), TextMobject("Compute Bayes' factor"), TextMobject("Multiply"), ) colors = [YELLOW, GREEN, BLUE] for step, label, color in zip(steps, step_labels, colors): step.set_color(color) step.next_to(label, RIGHT) self.play( LaggedStartMap(FadeIn, step_labels, shift=0.5 * RIGHT), LaggedStartMap(FadeIn, steps, shift=RIGHT), FadeOut(accurate_words), ) self.wait() # Step 2 details tex_to_color_map = { "+": GREEN, "\\text{Cancer}": YELLOW, "\\text{No cancer}": GREY_B, "=": WHITE, "\\over": WHITE, "{90\\%": GREEN, "9\\%}": GREEN, "1\\%}": TEAL, "10": WHITE, } bf_computation = TexMobject( """ { P(+ \\, | \\, \\text{Cancer}) \\over P(+ \\, | \\, \\text{No cancer}) } = {90\\% \\over 9\\%} = 10 """, tex_to_color_map=tex_to_color_map ) bf_computation[-1].scale(1.2) bf_computation.scale(0.6) bf_computation.next_to(step2, DOWN, aligned_edge=LEFT) bf_computation.save_state() bf_computation.scale(1.5) bf_computation.next_to(steps[1], RIGHT, LARGE_BUFF) lr_words = TextMobject("``Likelihood ratio''", font_size=30) lr_words.next_to(bf_computation[-1], RIGHT, MED_LARGE_BUFF, DOWN) lr_words.set_color(GREY_A) sens_part = bf_computation.get_part_by_tex("90\\%") sens_word = TextMobject("Sensitivity") sens_word.next_to(sens_part, UP, buff=1) sens_arrow = Arrow(sens_word.get_bottom(), sens_part.get_top(), buff=0.1) fpr_part = bf_computation.get_part_by_tex("9\\%") fpr_word = TextMobject("FPR") fpr_word.next_to(fpr_part, DOWN, buff=1) fpr_arrow = Arrow(fpr_word.get_top(), fpr_part.get_bottom(), buff=0.1) self.play(FadeIn(bf_computation, lag_ratio=0.1)) self.wait() self.play( Indicate(sens_part), FadeIn(sens_word), GrowArrow(sens_arrow) ) self.wait() self.play( Indicate(fpr_part), FadeIn(fpr_word), GrowArrow(fpr_arrow), ) self.wait(2) self.play( Restore(bf_computation), FadeOut(VGroup(sens_word, sens_arrow, fpr_word, fpr_arrow)) ) # Step 1 details step1_subtext = TextMobject("E.g.", " 1\\% ", " $\\rightarrow$ ", "1:99") step1_subtext.set_color(GREY_A) step1_subtext.scale(0.9) step1_subtext.next_to(step1, DOWN, aligned_edge=LEFT) step1_subtext.unlock_triangulation() self.play( FadeIn(step1_subtext[:2]), ShowIncreasingSubsets(population), ) self.wait() self.play( TransformFromCopy(step1_subtext[1], step1_subtext[3]), Write(step1_subtext[2]), ) self.play(ShowCreationThenFadeAround(step1_subtext[3])) self.wait() # Step 3 multiplication = TexMobject( "(", "1:99", ")", "\\times", "10", "=", "10:99", "\\rightarrow", "{10 \\over 109}", "\\approx", "{1 \\over 11}", font_size=36, ) multiplication.next_to(step3, DOWN, aligned_edge=LEFT) multiplication.unlock_triangulation() odds_rect = SurroundingRectangle(step1_subtext[-1], color=YELLOW) bf_rect = SurroundingRectangle(bf_computation[-1], color=GREEN) self.play( ShowCreation(odds_rect), ShowCreation(bf_rect), ) self.play( Write(VGroup(*(multiplication[i] for i in [0, 2, 3]))), TransformFromCopy(step1_subtext[-1], multiplication[1]), odds_rect.move_to, multiplication[1], odds_rect.set_opacity, 0, TransformFromCopy(bf_computation[-1], multiplication[4]), bf_rect.move_to, multiplication[4], bf_rect.set_opacity, 0, ) self.remove(odds_rect, bf_rect) self.wait() self.play( FadeIn(multiplication.get_part_by_tex("=")), TransformFromCopy( multiplication.get_part_by_tex("1:99"), multiplication.get_part_by_tex("10:99"), path_arc=30 * DEGREES, ), ) self.wait() self.play( FadeIn(multiplication.get_part_by_tex("\\rightarrow")), FadeIn(multiplication.get_part_by_tex("10 \\over 109")), ) self.play( FadeIn(multiplication.get_part_by_tex("\\approx")), FadeIn(multiplication.get_part_by_tex("1 \\over 11")), ) self.wait() # 10% prior example prior_odds = step1_subtext[1:] alt_prior_odds = TexMobject( "10\\%", "\\rightarrow", "\\text{1:9}" ) alt_prior_odds.match_height(prior_odds) alt_prior_odds.move_to(prior_odds, LEFT) alt_prior_odds.match_style(prior_odds) self.play( FadeOut(multiplication, shift=DOWN), FadeOut(prior_odds, shift=DOWN), ) self.play( FadeIn(alt_prior_odds[0], scale=2), ) self.play( population[:10].set_color, YELLOW, LaggedStartMap( ShowCreationThenFadeOut, VGroup(*( SurroundingRectangle(person, buff=0.05) for person in population[:10] )) ) ) self.wait() self.play( Write(alt_prior_odds[1]), FadeIn(alt_prior_odds[2], shift=0.5 * RIGHT) ) self.wait() new_multiplication = TexMobject( "(1:9)", "\\times", "10", "=", "10:9", "\\rightarrow", "{10 \\over 19}", "\\approx", "0.53", font_size=36, ) new_multiplication.move_to(multiplication, LEFT) rects = VGroup( SurroundingRectangle(alt_prior_odds.get_part_by_tex("1:9"), color=YELLOW), SurroundingRectangle(bf_computation.get_part_by_tex("10"), color=GREEN), SurroundingRectangle(new_multiplication.get_part_by_tex("10:9"), color=RED), ) self.play( FadeIn(rects[:2], lag_ratio=0.6, run_time=1.5) ) self.play( ReplacementTransform(rects[0], rects[2]), ReplacementTransform(rects[1], rects[2]), FadeIn(new_multiplication[:5]), ) self.wait() alt_rhs = TexMobject("> 1:1", font_size=36) alt_rhs.next_to(new_multiplication[4], RIGHT) self.play( FadeOut(rects[2]), Write(alt_rhs) ) self.wait() self.play( FadeOut(alt_rhs, shift=0.5 * UP), FadeIn(new_multiplication[5:7], shift=0.5 * UP), ) self.wait() self.play(Write(new_multiplication[7:])) self.wait() # Return to original prior self.play( FadeOut(new_multiplication, shift=DOWN), FadeOut(alt_prior_odds, shift=DOWN), FadeIn(prior_odds, shift=DOWN) ) self.play(population[1:].set_color, GREY_B) self.play(Indicate(population[0], color=RED)) self.play(LaggedStartMap( Indicate, population[1:], color=GREY_A, lag_ratio=0.01 )) self.wait() # Change test accuracy new_bf_computation = TexMobject( """ { P(+ \\, | \\, \\text{Cancer}) \\over P(+ \\, | \\, \\text{No cancer}) } = {90\\% \\over 1\\%} = 90 """, tex_to_color_map=tex_to_color_map ) new_bf_computation.replace(bf_computation) new_bf_computation[-2:].scale(1.5, about_edge=LEFT) rects = VGroup(*( SurroundingRectangle(bf_computation[i:j], buff=0.05) for (i, j) in [(5, 8), (14, 15)] )) rects.set_color(RED) self.play(ShowCreation(rects)) self.wait() self.play( FadeOut(bf_computation), FadeIn(new_bf_computation[:-2]) ) self.play(FadeOut(rects)) self.wait() self.play(Write(new_bf_computation[-2:])) self.wait() # New posterior (largely copy-pasted) final_multiplication = TexMobject( "(1:99)", "\\times", "90", "=", "90:99", "\\rightarrow", "{90 \\over 189}", "\\approx", "0.48", font_size=36, ) final_multiplication.move_to(multiplication, LEFT) rects = VGroup( SurroundingRectangle(step1_subtext.get_part_by_tex("1:99"), color=YELLOW), SurroundingRectangle(new_bf_computation.get_parts_by_tex("90")[1], color=GREEN), SurroundingRectangle(final_multiplication.get_part_by_tex("90:99"), color=RED), ) self.play( FadeIn(rects[:2], lag_ratio=0.6, run_time=1.5) ) self.play( ReplacementTransform(rects[0], rects[2]), ReplacementTransform(rects[1], rects[2]), FadeIn(final_multiplication[:5]), ) self.wait() alt_rhs = TexMobject("< 1:1", font_size=36) alt_rhs.next_to(final_multiplication[4], RIGHT) self.play( FadeOut(rects[2]), Write(alt_rhs) ) self.wait() self.play( FadeOut(alt_rhs, shift=0.5 * UP), FadeIn(final_multiplication[5:7], shift=0.5 * UP), ) self.wait() self.play(Write(final_multiplication[7:])) self.wait() class AskWhyItWorks(TeacherStudentsScene): def construct(self): self.student_says( "Huh? Why does\\\\that work?", target_mode="confused", ) self.change_student_modes( "pondering", "erm", "confused", look_at_arg=self.screen, ) self.play(self.teacher.change, "happy") self.wait(3) class WhyTheBayesFactorTrickWorks(Scene): def construct(self): # Setup before and after titles = VGroup( TextMobject("Before test"), TextMobject("After test"), ) titles.scale(1.25) for title, u in zip(titles, [-1, 1]): title.set_x(u * FRAME_WIDTH / 4) title.to_edge(UP, buff=MED_SMALL_BUFF) h_line = Line(LEFT, RIGHT) h_line.set_width(FRAME_WIDTH) h_line.next_to(titles, DOWN) h_line.set_x(0) v_line = Line(UP, DOWN).set_height(FRAME_HEIGHT) lines = VGroup(h_line, v_line) lines.set_stroke(GREY, 2) self.add(titles) self.add(lines) # Show population before population = VGroup(*(WomanIcon() for x in range(100))) population.arrange_in_grid(h_buff=0.7, fill_rows_first=False) population.set_height(5) population[:10].set_fill(YELLOW) population[:10].shift(MED_SMALL_BUFF * LEFT) population.match_x(titles[0]) population.to_edge(DOWN, buff=MED_LARGE_BUFF) w_tex = "(\\text{\\# With})" wo_tex = "(\\text{\\# Without})" t2c = { w_tex: YELLOW, wo_tex: GREY_B, } odds = TexMobject(w_tex, ":", wo_tex, tex_to_color_map=t2c) odds.next_to(population, UP, buff=MED_LARGE_BUFF) odds.match_x(titles[0]) self.add(population) self.add(odds) self.play( ShowCreationThenDestruction(Underline(odds[0])), LaggedStartMap( ShowCreationThenFadeOut, VGroup(*( SurroundingRectangle(icon, stroke_width=1, buff=0.05) for icon in population[:10] )) ), ) self.play( ShowCreationThenDestruction(Underline(odds[2])), LaggedStartMap( ShowCreationThenFadeOut, VGroup(*( SurroundingRectangle(icon, color=GREY, stroke_width=1, buff=0.05) for icon in population[10:] )), lag_ratio=0.01 ), ) self.wait() # Turn odds into fraction frac = TexMobject( w_tex, "\\over", wo_tex, tex_to_color_map=t2c, font_size=36 ) frac.next_to(h_line, DOWN, MED_LARGE_BUFF) frac.match_x(titles[0]) self.play( ReplacementTransform(odds, frac), population.set_height, 4.5, population.next_to, frac, DOWN, MED_LARGE_BUFF, ) self.wait() # Show filtration pop_copy = population.copy() pop_copy.match_x(titles[1]) tp_cases = pop_copy[:9] fn_cases = pop_copy[9:10] fp_cases = pop_copy[10::10] tn_cases = VGroup(*( case for case in pop_copy[10:] if case not in fp_cases )) VGroup(fn_cases, tn_cases).set_opacity(0.1) VGroup(tp_cases, tp_cases).set_stroke(GREEN, 3, background=True) pos_boxes = VGroup() for case in it.chain(tp_cases, fp_cases): box = SurroundingRectangle(case, buff=0.025) box.set_stroke(GREEN, 0) plus = TexMobject("+", font_size=24) plus.move_to(box.get_corner(UR)) plus.shift(DR * plus.get_height() / 4) plus.set_color(GREEN) box.add(plus) pos_boxes.add(box) self.play( TransformFromCopy(population, pop_copy), path_arc=30 * DEGREES ) self.play(LaggedStartMap(DrawBorderThenFill, pos_boxes)) self.wait() # Final fraction t2c.update({ "\\text{Cancer}": YELLOW, "\\text{No cancer}": GREY, "+": GREEN, "\\cdot": WHITE, }) final_frac = TexMobject( w_tex, "\\cdot P(+ \\,|\\, \\text{Cancer})", "\\over", wo_tex, "\\cdot P(+ \\,|\\, \\text{No cancer})", tex_to_color_map=t2c, font_size=36, ) final_frac.match_x(pop_copy) final_frac.match_y(frac) left_brace = Brace(tp_cases, LEFT) right_brace = Brace(fp_cases, RIGHT, min_num_quads=1) big_left_brace = Brace(VGroup(tp_cases, fn_cases), LEFT) big_right_brace = Brace(VGroup(fp_cases, tn_cases), RIGHT) big_left_brace.set_opacity(0) big_right_brace.set_opacity(0) self.play(*( TransformFromCopy( frac.get_part_by_tex(tex), final_frac.get_part_by_tex(tex) ) for tex in (w_tex, "\\over") )) self.wait() self.play( TransformFromCopy(big_left_brace, left_brace), Write(final_frac[1:7]) ) self.wait() self.play(*( TransformFromCopy( frac.get_part_by_tex(tex), final_frac.get_part_by_tex(tex) ) for tex in (wo_tex,) )) self.play( TransformFromCopy(big_right_brace, right_brace), Write(final_frac[9:]) ) self.wait() self.add(final_frac) # Circle likelihood ratio likelihood_ratio = VGroup( final_frac[2:7], final_frac[10:] ) lr_rect = SurroundingRectangle(likelihood_ratio) lr_rect.set_stroke(BLUE, 3) self.play(ShowCreation(lr_rect)) self.wait() class ReframeWhatTestsDo(TeacherStudentsScene): def construct(self): # Question question = TextMobject("What do tests tell you?") self.teacher_holds_up(question) self.wait() question.generate_target() question.target.set_height(0.6) question.target.center() question.target.to_edge(UP) self.play( MoveToTarget(question), *[ ApplyMethod(pi.change, "pondering", question.target) for pi in self.pi_creatures ] ) self.wait(2) # Possible answers answers = VGroup( TextMobject("Tests", " determine", " if you have", " a disease."), TextMobject("Tests", " determine", " your chances of having", " a disease."), TextMobject("Tests", " update", " your chances of having", " a disease."), ) students = self.students answers.set_color(BLUE_C) answers.arrange(DOWN) answers.next_to(question, DOWN, MED_LARGE_BUFF) answers[1][2].set_fill(GREY_A) answers[2][2].set_fill(GREY_A) answers[2][1].set_fill(YELLOW) def add_strike_anim(words): strike = Line() strike.replace(words, dim_to_match=0) strike.set_stroke(RED, 5) anim = ShowCreation(strike) words.add(strike) return anim self.play( GrowFromPoint(answers[0], students[0].get_corner(UR)), students[0].change, "raise_right_hand", answers[0], students[1].change, "sassy", students[0].eyes, students[2].change, "sassy", students[0].eyes, ) self.wait() self.play( add_strike_anim(answers[0]), students[0].change, "guilty", ) self.wait() answers[1][2].save_state() answers[1][2].replace(answers[0][2], stretch=True) answers[1][2].set_opacity(0) self.play( TransformFromCopy( answers[0][:2], answers[1][:2], ), TransformFromCopy( answers[0][3], answers[1][3], ), Restore(answers[1][2]), students[0].set_opacity, 0.5, students[0].change, "pondering", answers[1], students[1].change, "raise_right_hand", answers[1], students[2].change, "pondering", answers[1], ) self.wait(2) self.play( add_strike_anim(answers[1]), students[1].change, "guilty", ) self.wait(2) answers[2][1].save_state() answers[2][1].replace(answers[1][1], stretch=True) answers[2][1].set_opacity(0) self.play( *( TransformFromCopy( answers[1][i], answers[2][i], ) for i in [0, 2, 3] ), Restore(answers[2][1]), students[0].change, "pondering", answers[1], students[1].set_opacity, 0.5, students[1].change, "pondering", answers[1], students[2].change, "raise_left_hand", answers[1], ) self.play( self.teacher.change, "happy", students[2].eyes, ) self.wait() # The fundamental reframing new_title = TextMobject( "The Fundamental Reframing", font_size=72, ) new_title.add(Underline(new_title)) new_title.to_edge(UP) self.play( Write(new_title), FadeOut(question), self.students[2].change, "hooray", new_title, answers.shift, 0.5 * DOWN, ) self.wait(3) class PrevalenceVsPrior(Scene): def construct(self): # Prior and prevalence eq = TextMobject( "Prevalence = Prior", tex_to_color_map={ "Prevalence": WHITE, "Prior": YELLOW, } ) eq.shift(UP) prior = eq[2] prev = eq[0] strike = Line(eq.get_left(), eq.get_right()) strike.set_stroke(RED, 4) not_words = TextMobject("Not necessarily!") not_words.set_color(RED) not_words.match_width(eq) not_words.next_to(eq, DOWN, MED_LARGE_BUFF) factors = VGroup( TextMobject("Prevalence"), TextMobject("Symptoms"), TextMobject("Contacts\\\\", "(if contagious)"), ) factors.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) factors.shift(prev.get_center() - factors[0].get_center()) prior.generate_target() prior.target.match_y(factors[1]) prior.target.shift(0.5 * RIGHT) arrows = VGroup(*( Arrow( factor.get_right(), prior.target.get_corner(LEFT + u * UP), buff=0.1, max_tip_length_to_length_ratio=0.25, ) for u, factor in zip([1, 0, -1], factors) )) population = Population(100) population.set_height(7) population.to_edge(LEFT) population.set_fill(GREY_C) random.choice(population).set_color(YELLOW) self.play(Write(prev)) self.play(ShowIncreasingSubsets(population, run_time=3)) self.wait() self.play(Write(eq[1:])) self.wait() self.play( ShowCreation(strike), FadeIn(not_words, shift=0.2 * DOWN, rate_func=squish_rate_func(smooth, 0.3, 1)) ) self.wait() eq[1].unlock_triangulation() self.play( Uncreate(strike), MoveToTarget(prior), ReplacementTransform(eq[1], arrows), LaggedStartMap(FadeIn, factors[1:], shift=0.25 * DOWN), FadeOut(not_words, shift=DOWN) ) self.remove(prev) self.add(factors) self.wait() # Symptoms randy = Randolph(height=1.5) randy.to_corner(UR) randy.shift(LEFT) self.play(FadeIn(randy)) self.play( ShowCreationThenFadeOut(Underline(factors[1], color=RED)), randy.change, "sick", randy.set_color, SICKLY_GREEN, ) self.wait() # Contagion (implicit) self.play( ShowCreationThenFadeOut(Underline(factors[2][0], color=RED)), ShowCreationThenFadeOut(Underline(factors[2][1], color=RED)), ) self.wait() self.play( LaggedStartMap( FadeOut, VGroup( randy, prior, *arrows, *factors, ), shift=DOWN, ) ) # Everything above has been combed through after the rewrite class OldBayesFactorCode(Scene): def construct(self): # Show test statistics def get_prob_bars(p_positive): rects = VGroup(Square(), Square()) rects.set_width(0.2, stretch=True) rects.set_stroke(WHITE, 2) rects[0].stretch(p_positive, 1, about_edge=UP) rects[1].stretch(1 - p_positive, 1, about_edge=DOWN) rects[0].set_fill(GREEN, 1) rects[1].set_fill(RED_E, 1) braces = VGroup(*[ Brace(rect, LEFT, buff=SMALL_BUFF) for rect in rects ]) positive_percent = int(p_positive * 100) percentages = VGroup( TextMobject(f"{positive_percent}\\% +", color=GREEN), TextMobject(f"{100 - positive_percent}\\% $-$", color=RED), ) percentages.scale(0.7) for percentage, brace in zip(percentages, braces): percentage.next_to(brace, LEFT, SMALL_BUFF) result = VGroup( rects, braces, percentages, ) return result boxes = VGroup( Square(color=YELLOW), Square(color=GREY_B) ) boxes.set_height(3) labels = VGroup( TextMobject("With cancer"), TextMobject("Without cancer"), ) for box, label in zip(boxes, labels): label.next_to(box, UP) label.match_color(box) box.push_self_into_submobjects() box.add(label) boxes.arrange(DOWN, buff=0.5) boxes.to_edge(RIGHT) sens_bars = get_prob_bars(0.9) spec_bars = get_prob_bars(0.09) bar_groups = VGroup(sens_bars, spec_bars) for bars, box in zip(bar_groups, boxes): bars.shift(box[0].get_right() - bars[0].get_center()) bars.shift(0.75 * LEFT) box.add(bars) self.play( FadeIn(boxes[0], lag_ratio=0.1), post_group.scale, 0.1, {"about_point": FRAME_HEIGHT * DOWN / 2} ) self.wait() self.play( FadeIn(boxes[1], lag_ratio=0.1) ) self.wait() # Pull out Bayes factor ratio = VGroup( sens_bars[2][0].copy(), TexMobject("\\phantom{90\\%+} \\over \\phantom{9\\%+}"), spec_bars[2][0].copy(), *TexMobject("=", "10"), ) ratio.generate_target() ratio.target[:3].arrange(DOWN, buff=0.2) ratio.target[3:].arrange(RIGHT, buff=0.2) ratio.target[3:].next_to(ratio.target[:3], RIGHT, buff=0.2) ratio.target.center() ratio[1].scale(0) ratio[3:].scale(0) new_boxes = VGroup(boxes[0][0], boxes[1][0]).copy() new_boxes.generate_target() for box, part in zip(new_boxes.target, ratio.target[::2]): box.replace(part, stretch=True) box.scale(1.2) box.set_stroke(width=2) self.play( MoveToTarget(ratio), MoveToTarget(new_boxes), ) self.wait() for part, box in zip(ratio[0:3:2], new_boxes): part.add(box) self.remove(new_boxes) self.add(ratio) bayes_factor_label = TextMobject("``Bayes factor''") bayes_factor_label.next_to(ratio, UP, LARGE_BUFF) self.play(Write(bayes_factor_label)) self.wait() # Show updated result bayes_factor_label.generate_target() bayes_factor_label.target.scale(0.8) bayes_factor_label.target.next_to( post_group.saved_state[0], DOWN, buff=LARGE_BUFF, ) self.play( MoveToTarget(bayes_factor_label), ratio.scale, 0.8, ratio.next_to, bayes_factor_label.target, DOWN, Restore(post_group), FadeOut(boxes, shift=2 * RIGHT), ) self.wait() class AskAboutHowItsSoLow(TeacherStudentsScene): def construct(self): question = TextMobject( "How can it be 1 in 11\\\\" "if the test is accurate more\\\\" "than 90\\% of the time?", tex_to_color_map={ "1 in 11": BLUE, "90\\%": YELLOW, } ) self.student_says( question, student_index=1, target_mode="maybe", ) self.change_student_modes( "confused", "maybe", "confused", look_at_arg=question, ) self.wait() self.play(self.teacher.change, "tease", question) self.wait(2) class HowDoesUpdatingWork(TeacherStudentsScene): def construct(self): students = self.students teacher = self.teacher self.student_says( "Where are these\\\\numbers coming\\\\from?", target_mode="raise_right_hand" ) self.play( teacher.change, "happy", self.get_student_changes("confused", "erm", "raise_right_hand"), ) self.look_at(self.screen) self.wait(3) sample_pop = TextMobject("Sample populations (most intuitive)") bayes_factor = TextMobject("Bayes' factor (most fun)") for words in sample_pop, bayes_factor: words.move_to(self.hold_up_spot, DOWN) words.shift_onto_screen() bayes_factor.set_fill(YELLOW) self.play( RemovePiCreatureBubble(students[2]), teacher.change, "raise_right_hand", FadeIn(sample_pop, shift=UP, scale=1.5), self.get_student_changes( "pondering", "thinking", "pondering", look_at_arg=sample_pop, ) ) self.wait(2) self.play( FadeIn(bayes_factor, shift=UP, scale=1.5), sample_pop.shift, UP, teacher.change, "hooray", self.get_student_changes( "thinking", "confused", "erm", ) ) self.look_at(bayes_factor) self.wait(3) class HighlightBayesFactorOverlay(Scene): def construct(self): rect = Rectangle(height=2, width=3) rect.set_stroke(BLUE, 3) words = TextMobject("How to\\\\use this") words.next_to(rect, DOWN, buff=1.5) words.shift(2 * RIGHT) words.match_color(rect) arrow = Arrow( words.get_left(), rect.get_bottom(), path_arc=-60 * DEGREES ) arrow.match_color(words) self.play( FadeIn(words, scale=1.1), DrawBorderThenFill(arrow), ) self.play( ShowCreation(rect), ) self.wait() class BayesTheorem(Scene): def construct(self): # Add title title = TextMobject("Bayes' theorem", font_size=72) title.to_edge(UP, buff=MED_SMALL_BUFF) title.add(Underline(title)) self.add(title) # Draw rectangle prior = 1 / 12 sensitivity = 0.8 specificity = 0.9 rects = VGroup(*[Square() for x in range(4)]) # rects[0::2].set_stroke(GREEN, 3) # rects[1::2].set_stroke(RED, 3) rects.set_stroke(WHITE, 2) rects[:2].set_stroke(YELLOW) rects.set_fill(GREY_D, 1) rects.set_height(3) rects.set_width(3, stretch=True) rects.move_to(3.5 * LEFT) rects[:2].stretch(prior, 0, about_edge=LEFT) rects[2:].stretch(1 - prior, 0, about_edge=RIGHT) rects[0].stretch(sensitivity, 1, about_edge=UP) rects[1].stretch(1 - sensitivity, 1, about_edge=DOWN) rects[2].stretch(1 - specificity, 1, about_edge=UP) rects[3].stretch(specificity, 1, about_edge=DOWN) rects[0].set_fill(GREEN_D) rects[1].set_fill(interpolate_color(RED_E, BLACK, 0.5)) rects[2].set_fill(GREEN_E) rects[3].set_fill(interpolate_color(RED_E, BLACK, 0.75)) icons = VGroup(*(WomanIcon() for x in range(120))) icons.arrange_in_grid( 10, 12, h_buff=1, v_buff=0.5, fill_rows_first=False, ) icons.replace(rects, dim_to_match=1) icons.scale(0.98) icons[:10].set_fill(YELLOW) # Add terminology braces = VGroup( Brace(rects[1], DOWN, buff=SMALL_BUFF), *( Brace(rect, u * RIGHT, buff=SMALL_BUFF) for rect, u in zip(rects, [-1, -1, 1, 1]) ) ) braces.set_fill(GREY_B) terms = VGroup( TextMobject("Prior"), TextMobject("Sensitivity"), TextMobject("False\\\\negative\\\\rate"), TextMobject("False\\\\positive\\\\rate"), TextMobject("Specificity"), ) terms[0].set_color(YELLOW) for term, brace in zip(terms, braces): term.scale(0.5) term.next_to(brace, brace.direction, buff=SMALL_BUFF) # Formula with jargon lhs = TexMobject( "P(", "\\text{Sick}", "\\text{ given }", "\\text{+}", ")" ) lhs.set_color_by_tex("Sick", YELLOW) lhs.set_color_by_tex("+", GREEN) lhs.set_x(3.5) lhs.set_y(2) equals = TexMobject("=") equals.rotate(PI / 2) equals.scale(1.5) term_formula = TexMobject( """ {(\\text{Prior})(\\text{Sensitivity}) \\over (\\text{Prior})(\\text{Sensitivity}) + (1 - \\text{Prior})(\\text{FPR}) """, font_size=30, tex_to_color_map={ "\\text{Prior}": YELLOW, "\\text{Sensitivity}": GREEN, "\\text{FPR}": GREEN_D, } ) eq2 = equals.copy() prob_formula = TexMobject( """ P(\\text{Sick}) P(\\text{+} \\text{ given } \\text{Sick}) \\over P(\\text{+}) """, tex_to_color_map={ "\\text{Sick}": YELLOW, "\\text{+}": GREEN, }, font_size=30 ) formula = VGroup(lhs, equals, term_formula, eq2, prob_formula) formula.arrange(DOWN, buff=0.35) formula.to_edge(RIGHT) # Animations rects.set_opacity(0) rects.submobjects.reverse() icons.save_state() icons.set_height(6) icons.center().to_edge(DOWN) self.add(icons) self.wait() self.play(Restore(icons)) self.add(rects, icons) self.play( rects.set_opacity, 1, icons.set_opacity, 0.1, LaggedStartMap(GrowFromCenter, braces), LaggedStartMap(FadeIn, terms), ) self.wait() self.play(LaggedStartMap(FadeIn, formula, scale=1.2, run_time=2)) self.wait() # Show confused randy = Randolph(height=1.5) randy.to_edge(DOWN) self.play(FadeIn(randy)) self.play(randy.change, 'maybe', formula) self.play(Blink(randy)) self.play(randy.change, 'confused', formula) self.wait() self.play(Blink(randy)) self.wait() # Back to population self.add(rects, icons) self.play( icons.set_opacity, 1, rects.set_opacity, 0.5, randy.change, "pondering", icons, ) self.play(Blink(randy)) self.wait() class CompressedBayesFactorSteps(Scene): def construct(self): steps = TextMobject( "Step 1) ", "Express the prior with odds\\\\", "Step 2) ", "Compute the Bayes factor\\\\", "Step 3) ", "Multiply\\\\", alignment="", ) steps[1].set_color(YELLOW) steps[3].set_color(GREEN) steps[5].set_color(BLUE) steps.set_width(FRAME_WIDTH - 2) self.add(steps) class LookOverTweet(Scene): def construct(self): # Tweet bg_rect = FullScreenFadeRectangle() bg_rect.set_fill(GREY_E, 1) self.add(bg_rect) tweet = ImageMobject("twitter_covid_test_poll") tweet.set_height(6) tweet.to_edge(LEFT) self.play(FadeIn(tweet, shift=UP, scale=1.2)) self.wait() # Tweet highlights lines = VGroup( Line((-3.9, 1.7), (0.3, 1.7)), Line((-3.4, 0.1), (-0.7, 0.1)), VGroup( Line((0.2, 0.1), (0.8, 0.1)), Line((-6.4, -0.3), (-4, -0.3)), ), ) lines.set_color(RED) rects = VGroup(*(Rectangle(3.2, 0.37) for x in range(4))) rects.arrange(DOWN, buff=0.08) rects.set_stroke(BLUE, 3) rects.set_fill(BLUE, 0.25) rects.move_to((-4.8, -1.66, 0.)) # Population icon = SVGMobject("person") icon.set_stroke(width=0) globals()['icon'] = icon population = VGroup(*(icon.copy() for x in range(1000))) population.scale(1 / population[0].get_height()) population.arrange_in_grid( 30, 33, buff=MED_LARGE_BUFF, ) population.set_width(4) population.to_edge(RIGHT) population.set_fill(GREY_B) population[random.randint(0, 1000)].set_fill(YELLOW) pop_title = TextMobject("1", " in ", "1,000") pop_title[0].set_color(YELLOW) pop_title.next_to(population, UP) self.play( FadeIn(pop_title, shift=0.25 * UP), ShowIncreasingSubsets(population), ShowCreation(lines[0]) ) self.wait() pop_group = VGroup(population, pop_title) # Positive result clipboard = SVGMobject("clipboard") clipboard.set_stroke(width=0) clipboard.set_fill(interpolate_color(GREY_BROWN, WHITE, 0.5), 1) clipboard.set_width(2.5) clipboard.move_to(population) clipboard.to_edge(UP) result = TextMobject( "+\\\\", "SARS\\\\CoV-2\\\\", "Detected" ) result[0].scale(1.5, about_edge=DOWN) result[0].set_fill(GREEN) result[0].set_stroke(GREEN, 2) result[-1].set_fill(GREEN) result.set_width(clipboard.get_width() * 0.7) result.move_to(clipboard) result.shift(0.2 * DOWN) clipboard.add(result) self.play( pop_group.scale, 0.4, pop_group.to_edge, DOWN, FadeIn(clipboard, DOWN), ) self.wait() # FPR, FRN self.play(ShowCreation(lines[1])) self.wait() self.play(ShowCreation(lines[2])) self.wait() # Answer choices rect = rects[0].copy() self.play(DrawBorderThenFill(rect)) self.wait() for r2 in rects[1:]: self.play(Transform(rect, r2)) self.wait() self.play(FadeOut(rect)) # Focus on FPR rect = Rectangle() rect.match_width(lines[1]) rect.scale(1.02) rect.set_height(0.4, stretch=True) rect.match_style(lines[1]) rect.move_to(lines[1], DOWN) rect.set_fill(RED, 0.2) self.play( FadeOut(lines[0]), FadeOut(lines[2]), DrawBorderThenFill(rect) ) # Randy randy = Randolph(height=2) randy.flip() randy.next_to(tweet, RIGHT, LARGE_BUFF, DOWN) bubble = randy.get_bubble(height=3, width=3, direction=RIGHT) bubble.flip() bubble.write("99\\%\\\\right?") self.play( FadeIn(randy), FadeOut(clipboard, RIGHT), FadeOut(pop_group, 0.5 * RIGHT), ) self.play( randy.change, "maybe", DrawBorderThenFill(bubble), Write(bubble.content), ) self.play(Blink(randy)) self.wait() class DisambiguateFPR(Scene): def construct(self): # Add title title = TextMobject( "1\\% False Positive Rate", substrings_to_isolate=["F", "P", "R"], font_size=72 ) title.to_edge(UP) title.set_color(GREY_A) underline = Underline(title) underline.match_color(title) morty = Mortimer() morty.to_corner(DR) morty.look_at(title) self.add(title) self.play( PiCreatureSays( morty, "It's a confusing term!", target_mode="surprised", run_time=1, ) ) self.play( Blink(morty), ShowCreation(underline), ) self.play(morty.change, "angry") self.play(Blink(morty)) # Draw out grid word_grid = VGroup( TextMobject("True positives", color=GREEN), TextMobject("False negatives", color=RED_E), TextMobject("False positives", color=GREEN_D), TextMobject("True negatives", color=RED), ) word_box = SurroundingRectangle(word_grid) word_box.set_stroke(WHITE, 2) word_box.stretch(2, 1) word_grid.arrange_in_grid( v_buff=1.0, h_buff=0.5, fill_rows_first=False ) word_grid.move_to(2 * DOWN) globals()['word_box'] = word_box word_boxes = VGroup(*( word_box.copy().move_to(word).match_color(word) for word in word_grid )) group_boxes = VGroup( SurroundingRectangle(word_boxes[:2], color=YELLOW), SurroundingRectangle(word_boxes[2:], color=GREY_B), ) group_titles = VGroup( TextMobject("Have COVID-19", font_size=36), TextMobject("Don't have COVID-19", font_size=36), ) for box, gt in zip(group_boxes, group_titles): gt.next_to(box, UP) gt.match_color(box) icon = SVGMobject("person", stroke_width=0, fill_color=GREY_B) globals()['icon'] = icon with_pop = VGroup(*(icon.copy() for x in range(1))) with_pop.set_color(YELLOW) without_pop = VGroup(*(icon.copy() for x in range(999))) with_pop.set_height(0.5) with_pop.move_to(group_boxes[0]) without_pop.arrange_in_grid(n_rows=20) without_pop.replace(group_boxes[1], dim_to_match=1) without_pop.scale(0.9) self.play( ShowCreation(group_boxes, lag_ratio=0.3), DrawBorderThenFill(with_pop), ShowIncreasingSubsets(without_pop), FadeIn(group_titles, lag_ratio=0.3), FadeOut(morty), FadeOut(morty.bubble), FadeOut(morty.bubble.content), ) self.wait() arrow = Vector(DOWN) arrow.next_to(group_titles[1], UP) self.play(GrowArrow(arrow)) self.wait() self.play( LaggedStartMap(Write, word_grid[2:], lag_ratio=0.9), LaggedStartMap(ShowCreation, word_boxes[2:], lag_ratio=0.9), without_pop.set_opacity, 0.3, run_time=1, ) self.wait() # Two reasonable fractions tp, fn, fp, tn = ( word for word, box in zip(word_grid, word_boxes) ) def create_fraction(m1, m2, scale_factor=0.75): result = VGroup( m1.copy(), TexMobject("\\quad \\over \\quad"), m1.copy(), TexMobject("+"), m2.copy() ) result[0::2].scale(scale_factor) result[2:].arrange(RIGHT, buff=SMALL_BUFF) result[1].match_width(result[2:], stretch=True) result[1].next_to(result[2:], UP, SMALL_BUFF) result[0].next_to(result[1], UP, SMALL_BUFF) return result def fraction_anims(m1, m2, fraction): return [ TransformFromCopy(m1, fraction[0]), TransformFromCopy(m1, fraction[2]), TransformFromCopy(m2, fraction[4]), GrowFromPoint( fraction[1], VGroup(m1, m2).get_center(), ), GrowFromPoint( fraction[3], VGroup(m1, m2).get_center(), ), ] frac1 = create_fraction(fp, tp) frac2 = create_fraction(fp, tn) fracs = VGroup(frac1, frac2) fracs.arrange(RIGHT, buff=3) fracs.to_edge(UP, buff=1.5) title.add(underline) fpr = TextMobject( "1\\% ", "F", "", "P", "", "R", "", font_size=72 ) fpr.add(Underline(fpr)) fpr[-1].set_width(0) fpr.match_style(title) fpr.next_to(frac2, UP, MED_LARGE_BUFF) frac2.save_state() frac2.set_x(0) title.unlock_triangulation() self.play( FadeOut(arrow), LaggedStart(*fraction_anims(fp, tn, frac2)) ) self.wait(2) self.play( ReplacementTransform(title, fpr), Restore(frac2) ) self.play( LaggedStartMap(Write, word_grid[:2], lag_ratio=0), LaggedStartMap(ShowCreation, word_boxes[:2], lag_ratio=0), with_pop.set_opacity, 0.2, run_time=1, ) self.play( LaggedStart(*fraction_anims(fp, tp, frac1)), fn.set_opacity, 0.5, tn.set_opacity, 0.5, ) self.wait() comment = TextMobject("What we actually want") comment.next_to(frac1, UP, buff=0.75) self.play(FadeIn(comment, 0.5 * UP)) self.wait() # Prep for Bayes calculation right_brace = Brace(word_boxes[2], RIGHT, buff=SMALL_BUFF) left_brace = Brace(word_boxes[1], LEFT, buff=SMALL_BUFF) fpr.generate_target() fpr.target.scale(48 / 72) fpr.target.next_to(right_brace, RIGHT) fnr = TextMobject("10\\% FNR") fnr.next_to(left_brace, LEFT) VGroup(left_brace, right_brace, fnr).set_color(GREY_A) self.play( MoveToTarget(fpr), GrowFromCenter(left_brace), GrowFromCenter(right_brace), FadeIn(fnr), FadeOut(comment), FadeOut(fracs), fn.set_opacity, 1.0, tn.set_opacity, 1.0, ) prior_title = TextMobject("Prior: 1 in 1,000", font_size=72) prior_title.to_edge(UP) self.play(FadeIn(prior_title, UP)) self.wait() # Show population counts full_pop = VGroup( TextMobject("Imagine"), Integer(10000), TextMobject("People") ) full_pop.arrange(RIGHT, aligned_edge=DOWN) full_pop.set_color(GREY_A) full_pop.next_to(prior_title, DOWN, MED_LARGE_BUFF) self.play( FadeIn(full_pop[0::2]), CountInFrom(full_pop[1], 0), FadeOut(with_pop), FadeOut(without_pop), ) self.wait() def move_number_above_word(number, word, target_count, buff=MED_SMALL_BUFF, scene=self): mover = number.copy() mover.original_bottom = mover.get_bottom() start_count = number.get_value() scene.play( UpdateFromAlphaFunc( mover, lambda m, a, word=word, sc=start_count, tc=target_count, buff=buff: m.set_color( interpolate_color(GREY_A, word.get_color(), a) ).set_value( interpolate(sc, tc, a), ).move_to( interpolate( m.original_bottom, word.get_top() + buff * UP, a ), DOWN, ) ) ) return mover with_count = move_number_above_word(full_pop[1], group_titles[0], 10, buff=0.3) self.wait() without_count = move_number_above_word(full_pop[1], group_titles[1], 9990) self.wait() self.play(word_grid.shift, 0.25 * DOWN) tp_count = move_number_above_word(with_count, word_grid[0], 9, SMALL_BUFF) fn_count = move_number_above_word(with_count, word_grid[1], 1, SMALL_BUFF) self.wait() fp_count = move_number_above_word(without_count, word_grid[2], 100, SMALL_BUFF) tn_count = move_number_above_word(without_count, word_grid[3], 9890, buff=0.05) self.wait() faders = VGroup( with_count, without_count, group_titles, fpr, fnr, fn, tn, fn_count, tn_count, ) braces = VGroup(left_brace, right_brace) self.play(faders.set_opacity, 0.3, FadeOut(braces)) self.wait() # Show posterior prior_group = VGroup(prior_title, full_pop) prior_group.generate_target() prior_group.target.to_edge(LEFT) prior_group.target.shift(0.5 * DOWN) arrow = Vector(0.8 * RIGHT) arrow.next_to(prior_group.target[0], RIGHT) post_word = TextMobject("Posterior:", font_size=72) post_word.set_color(BLUE) post_word.next_to(arrow, RIGHT) post_word.align_to(prior_group.target[0][0][0], DOWN) post_frac = create_fraction(tp_count, fp_count, scale_factor=1) post_frac.next_to(post_word, RIGHT, MED_LARGE_BUFF) rhs = TexMobject("\\approx", "8.3\\%") rhs.next_to(post_frac, RIGHT) self.play( MoveToTarget(prior_group), GrowArrow(arrow), Write(post_word, run_time=1), ) self.play(*fraction_anims(tp_count, fp_count, post_frac)) self.wait() self.play(Write(rhs)) self.wait() class BayesFactorForCovidExample(Scene): def construct(self): # Titles titles = VGroup( TextMobject("Prior odds", color=YELLOW), TextMobject("Bayes factor", color=GREEN), TextMobject("Posterior odds", color=BLUE), ) titles.scale(1.25) titles[0].shift(FRAME_WIDTH * LEFT / 3) titles[2].shift(FRAME_WIDTH * RIGHT / 3) titles.to_edge(UP) v_lines = VGroup(*( Line(UP, DOWN).set_height(FRAME_HEIGHT).move_to( FRAME_WIDTH * x * RIGHT / 6, ) for x in (-1, 1) )) self.add(titles, v_lines) # Prior odds prior_odds = TextMobject( "1", "\\,:\\,", "999", tex_to_color_map={"1": YELLOW} ) prior_odds.next_to(titles[0], DOWN, buff=1) population = Population(1000) population.set_width(FRAME_WIDTH / 5) population[0].set_color(YELLOW) population[0].next_to(population[1:], LEFT, MED_LARGE_BUFF) colon = TexMobject(":") colon.move_to(midpoint(population[0].get_right(), population[1:].get_left())) pop_ratio = VGroup(population[0], colon, population[1:]) pop_ratio.next_to(prior_odds, DOWN, MED_LARGE_BUFF) self.play( FadeIn(prior_odds, shift=0.2 * DOWN, scale=1.2), FadeIn(pop_ratio, lag_ratio=0.01), ) self.wait() # Bayes factor bf_lhs = TexMobject( """ { P(+ \\,|\\, \\text{COVID}) \\over P(+ \\,|\\, \\text{No COVID}) } """, tex_to_color_map={ "+": GREEN, "\\text{COVID}": YELLOW, "\\text{No COVID}": GREY_A, } ) bf_lhs.next_to(titles[1], DOWN) bf_rhs = TexMobject( "= {90\\%", "\\over", "1\\%}", tex_to_color_map={ "90\\%": GREEN_D, "1\\%": GREEN, } ) bf = VGroup(bf_lhs, bf_rhs) bf.arrange(RIGHT) bf.set_width(0.9 * FRAME_WIDTH / 3) bf.next_to(titles[1], DOWN, MED_LARGE_BUFF) eq_90 = TexMobject("=", "90") eq_90.next_to(bf, DOWN, MED_LARGE_BUFF) eq_90[1].save_state() eq_90[1].replace(bf_rhs[1:], stretch=True) eq_90[1].set_opacity(0) eq_90[1].set_color(GREEN) self.play( FadeIn(bf_lhs, lag_ratio=0.1), FadeIn(bf_rhs[0::2], lag_ratio=0.1), ) self.wait() self.play(Write(bf_rhs[1])) self.wait() self.play(Write(bf_rhs[3])) self.wait() self.play( TransformFromCopy(bf_rhs[0], eq_90[0]), Restore(eq_90[1]) ) self.wait() # Posterior post_odds = TexMobject("90", ":", "999") post_odds.match_x(titles[2]) post_odds.match_y(prior_odds) arrow = Vector(DOWN) arrow.next_to(post_odds, DOWN) fraction = TexMobject( "{90", "\\over", "90", "+", "999}", "\\approx", "8.3\\%" ) fraction.next_to(arrow, DOWN) prior_odds.unlock_triangulation() self.play(LaggedStart( TransformFromCopy(eq_90[1], post_odds[0]), TransformFromCopy(prior_odds[1:], post_odds[1:]), lag_ratio=0.4 )) self.wait() self.play( GrowArrow(arrow), FadeIn(fraction, DOWN) ) # Approximate approx1 = TexMobject("\\approx", "90", ":", "1,000") approx2 = TexMobject("\\approx", "9", ":", "100") approx3 = TexMobject("\\approx 9\\%") approx1.next_to(arrow, DOWN) approx2.move_to(approx1) arrow2 = arrow.copy() arrow2.next_to(approx2, DOWN) approx3.next_to(arrow2, DOWN) self.play( FadeOut(fraction, 0.5 * DOWN), FadeIn(approx1, 0.5 * DOWN), ) self.wait() approx1.unlock_triangulation() self.play( ReplacementTransform(approx1, approx2) ) self.wait() self.play( GrowArrow(arrow2), FadeIn(approx3, DOWN) ) self.wait() class WhyIsThisWrong(TeacherStudentsScene): def construct(self): # Ask question tweet = ImageMobject("twitter_covid_test_poll") tweet.replace(self.screen, dim_to_match=0) tweet.to_edge(UP) tweet.scale(0.9, about_edge=UP) self.add(tweet) self.student_says( "Bayes' rule says\\\\ 8.5\\%, right?", target_mode="hooray", student_index=1, ) self.wait() self.play( PiCreatureSays( self.teacher, "Well...", bubble_kwargs={"height": 2, "width": 2}, target_mode="hesitant", ), self.get_student_changes("confused", "erm", "pondering") ) self.wait(3) # Highlight symptoms self.play( RemovePiCreatureBubble(self.students[1]), RemovePiCreatureBubble(self.teacher, target_mode="raise_right_hand"), tweet.move_to, self.hold_up_spot, DR, tweet.shift, RIGHT, ) underline = Line((-1.3, 2.65), (2.1, 2.65)) underline.shift(RIGHT) underline.set_stroke(RED, 3) self.play( ShowCreation(underline), self.teacher.look_at, underline, *(ApplyMethod(pi.change, "pondering", underline) for pi in self.students), ) self.wait() # Change prior ineq = TextMobject("Prior > $\\frac{1}{1,000}$", font_size=72) ineq.next_to(tweet, LEFT, LARGE_BUFF) self.play(Write(ineq)) self.look_at(ineq) self.wait() # More severe symptoms for pi in self.students: pi.generate_target() pi.target.change("sick") pi.target.set_color(SICKLY_GREEN) pi.target.look(DR) self.play( LaggedStartMap(MoveToTarget, self.students, lag_ratio=0.3), self.teacher.change, "erm" ) clipboard = get_covid_clipboard() clipboard.to_corner(UL) self.play( FadeIn(clipboard, UP), FadeOut(ineq, UP), *(ApplyMethod(pi.look_at, clipboard) for pi in self.pi_creatures), ) self.wait() percent = TexMobject("8.3 \\%", font_size=72) percent.next_to(clipboard, RIGHT, buff=0.6) percent_cross = Cross(percent, stroke_width=8) self.play( FadeIn(percent), self.teacher.change, "tease" ) self.play(ShowCreation(percent_cross)) self.wait() self.student_says( "What's the appropriate\\\\math here?", student_index=1, added_anims=[ FadeOut(tweet), FadeOut(underline), self.teacher.change, "happy" ] ) self.wait(2) class TotalPopulationVsSymptomatic(Scene): def construct(self): # Titles v_line = Line(UP, DOWN).set_height(2 * FRAME_HEIGHT) v_line.to_edge(UP) self.add(v_line) titles = VGroup( TextMobject("Population"), TextMobject("Population", " \\emph{with symptoms}"), ) titles[1][1].set_color(BLUE) for title, u in zip(titles, [-1, 1]): title.move_to(u * RIGHT * FRAME_WIDTH / 4) titles.to_edge(UP) self.add(titles[0]) # Actual populations full_pop = VGroup(*(Dot() for x in range(10000))) full_pop.arrange_in_grid( buff=full_pop[0].get_width() ) full_pop.set_width(FRAME_WIDTH / 2 - 1) full_pop.next_to(titles[0], DOWN) full_pop.to_edge(DOWN, buff=SMALL_BUFF) covid_pop = full_pop[:10] covid_pop.set_fill(YELLOW) covid_pop.save_state() covid_pop.scale(8, about_edge=UL) covid_pop.shift(DOWN) full_pop_words = VGroup( TextMobject("10 ", "with COVID", color=YELLOW), TextMobject("9990 ", "without COVID", color=GREY_B), ) full_pop_words.arrange(RIGHT, buff=LARGE_BUFF) full_pop_words.scale(0.7) full_pop_words.next_to(full_pop, UP, SMALL_BUFF, LEFT) self.play( FadeIn(full_pop_words[0]), Write(covid_pop), run_time=1 ) self.play( FadeIn(full_pop_words[1]), ShowIncreasingSubsets(full_pop[10:]), Restore(covid_pop), ) self.wait() # Mention symptoms self.play( TransformFromCopy(titles[0][0], titles[1][0]), FadeInFromPoint(titles[1][1], titles[0].get_center()), ) self.wait() arrow = Vector(DOWN).next_to(titles[1][1], DOWN) arrow.set_color(BLUE) prop_ex = TexMobject("\\sim \\frac{1}{50}", "\\text{, say}") prop_ex[0].set_color(BLUE) prop_ex.next_to(arrow, DOWN) self.play( GrowArrow(arrow), FadeIn(prop_ex, DOWN) ) self.wait() # Symptomatic population left_sym_pop = VGroup(*random.sample(list(full_pop[10:]), 200)) sym_pop = left_sym_pop.copy() sym_pop.generate_target() buff = get_norm(full_pop[1].get_left() - full_pop[0].get_right()) sym_pop.target.arrange_in_grid(buff=buff) sym_pop.target.set_width(3.5) sym_pop.target.match_x(titles[1]) sym_pop.target.align_to(full_pop, UP) sym_pop.target.shift(DOWN) sym_pop.target.set_color(BLUE) sym_pop_label = TextMobject("$\\sim 200$", " without COVID") sym_pop_label.set_color(GREY_B) sym_pop_label.scale(0.7) sym_pop_label.next_to(sym_pop.target, UP, buff=0.2) self.play( sym_pop.set_color, BLUE, MoveToTarget(sym_pop, lag_ratio=0.01, run_time=2), prop_ex.to_edge, DOWN, FadeOut(arrow), Write(sym_pop_label), ) self.wait() sym_covid_pop = covid_pop[:5].copy() sym_covid_pop.generate_target() sym_covid_pop.target.arrange(DOWN, buff=buff) sym_covid_pop.target.scale( sym_pop[0].get_height() / sym_covid_pop[0].get_height() ) sym_covid_pop.target.next_to(sym_pop, LEFT, aligned_edge=UP) sym_covid_pop_label = TextMobject("5", " with COVID") sym_covid_pop_label.scale(0.7) sym_covid_pop_label.next_to(sym_covid_pop.target, UP, buff=0.2) sym_covid_pop_label.set_color(YELLOW) self.play(ShowCreationThenFadeAround(sym_pop_label)) self.play( VGroup(sym_pop, sym_pop_label).to_edge, RIGHT, MoveToTarget(sym_covid_pop), FadeIn(sym_covid_pop_label), ) self.wait() # Apply test left_movers = VGroup( full_pop, full_pop_words, titles[0], v_line, ) right_movers = VGroup( titles[1], sym_covid_pop, sym_covid_pop_label, sym_pop, sym_pop_label, ) v_line_copy = v_line.copy() left_vect = (FRAME_WIDTH + SMALL_BUFF) * LEFT / 2 self.play( LaggedStartMap( ApplyMethod, left_movers, lambda m: (m.shift, left_vect), lag_ratio=0.05, ), ApplyMethod( right_movers.shift, left_vect, rate_func=squish_rate_func(smooth, 0.3, 1), ), FadeOut(prop_ex), run_time=2, ) self.wait() # With test result right_title = TextMobject( "Population ", "\\emph{with symptoms}\\\\", "and a positive test result" ) right_title.set_color_by_tex("symptoms", BLUE) right_title.set_color_by_tex("positive", GREEN) right_title.set_x(FRAME_WIDTH / 4) right_title.to_edge(UP) titles.add(right_title) self.play( ShowCreation(v_line_copy), FadeIn(right_title), ) self.wait() right_vect = FRAME_WIDTH * RIGHT / 2 pc_pop = sym_covid_pop.copy() pn_pop = VGroup(*random.sample(list(sym_pop), 2)).copy() pn_pop.generate_target() buff = get_norm(sym_pop[1].get_left() - sym_pop[0].get_right()) pn_pop.target.arrange(DOWN, buff=buff) pn_pop.target.next_to(sym_pop_label, DOWN) pn_pop.target.shift(right_vect) pn_pop.target.scale(1.5, about_edge=UP) black_rect = BackgroundRectangle(pc_pop[-1], fill_opacity=1) black_rect.stretch(0.5, 1, about_edge=DOWN) pc_pop.add(black_rect) pc_pop.set_opacity(0) pc_pop_label = TextMobject("4.5 with COVID", color=YELLOW) pn_pop_label = TextMobject("~2 without COVID", color=GREY_B) labels = VGroup(pc_pop_label, pn_pop_label) labels.scale(0.7) labels.set_opacity(0) pc_pop_label.move_to(sym_covid_pop_label) pn_pop_label.move_to(sym_pop_label) def get_test_rects(mob): return VGroup(*( SurroundingRectangle( part, color=GREEN, buff=buff / 2, stroke_width=2, ) for part in mob )) self.play( pc_pop_label.shift, right_vect, pc_pop_label.set_opacity, 1, pc_pop.shift, right_vect, pc_pop.set_opacity, 1, pc_pop.scale, 1.5, {"about_edge": UP} ) pc_pop_rects = get_test_rects(pc_pop[:5]) self.play(FadeIn(pc_pop_rects, lag_ratio=0.3)) self.wait() self.play( pn_pop_label.shift, right_vect, pn_pop_label.set_opacity, 1, MoveToTarget(pn_pop), ) pn_pop_rects = get_test_rects(pn_pop) self.play(FadeIn(pn_pop_rects, lag_ratio=0.3)) self.wait() # Final fraction equation = TexMobject( "{4.5 \\over 4.5 + {2}} \\approx 69.2\\%", tex_to_color_map={ "4.5": YELLOW, "{2}": GREY_B, } ) equation.move_to(FRAME_WIDTH * RIGHT / 4) equation.to_edge(DOWN, buff=LARGE_BUFF) self.play(FadeIn(equation, lag_ratio=0.1)) self.wait() # Zoom out frame = self.camera.frame self.play( frame.scale, 1.5, {"about_edge": UR}, run_time=3, ) self.wait() # Prepare population groups to move pop_groups = VGroup( VGroup( full_pop, full_pop_words, ), VGroup( sym_covid_pop, sym_covid_pop_label, sym_pop, sym_pop_label, ), VGroup( pc_pop, pc_pop_label, pc_pop_rects, pn_pop, pn_pop_label, pn_pop_rects, equation ), ) pop_groups.generate_target() for group in pop_groups.target: group.scale(0.5, about_edge=DOWN) group.shift(4.5 * DOWN) pop_groups.target[0][0].set_opacity(0.5) h_line = Line(pop_groups.get_left(), pop_groups.get_right()) h_line.next_to(pop_groups.target, UP) h_line.set_stroke(WHITE, 1) # Relevant odds labels def get_odds_label(n, k, n_color=YELLOW, k_color=GREY_B): result = VGroup( TextMobject("Odds = "), Integer(n, color=n_color, edge_to_fix=UR), TextMobject(":"), Integer(k, color=k_color, edge_to_fix=UL), ) result.scale(1.5) result.arrange(RIGHT, buff=0.25) result[3].align_to(result[1], UP) return result odds_labels = VGroup( get_odds_label(1, 999), get_odds_label(25, 1000), get_odds_label(40, 90), ) for label, title in zip(odds_labels, titles): label.move_to(title, UP) label.shift(2 * DOWN) odds_underlines = VGroup(*( Underline(label[1:]) for label in odds_labels )) odds_arrows = VGroup(*( Arrow(o1.get_right(), o2.get_left()) for o1, o2 in zip(odds_labels, odds_labels[1:]) )) odds_arrows.set_fill(GREY_B) for arrow in odds_arrows: arrow.set_angle(0) self.play( MoveToTarget(pop_groups), FadeIn(odds_labels[0]), ShowCreation(h_line), ) for i in (1, 2): self.play( FadeIn(odds_labels[i][0], RIGHT), FadeIn(odds_underlines[i], RIGHT), GrowArrow(odds_arrows[i - 1]), ) self.wait() # Bayes factors bf_labels = VGroup( TextMobject( "Bayes factor for symptoms", tex_to_color_map={"for symptoms": BLUE} ), TextMobject( "Bayes factor positive test", tex_to_color_map={"positive test": GREEN} ), ) for label, title in zip(bf_labels, titles[1:]): label.move_to(title, UP) label.shift(4.5 * DOWN) t2c = { "\\text{COVID}": YELLOW, "\\text{No COVID}": GREY_B, "\\text{Symp}": BLUE, "+": GREEN, "=": WHITE, } bf_equations = VGroup( TexMobject( """ {P(\\text{Symp} \\,|\\, \\text{COVID}) \\over P(\\text{Symp} \\,|\\, \\text{No COVID})} = {50\\% \\over 2\\%} = 25 """, tex_to_color_map=t2c, ), TexMobject( """ {P(+ \\,|\\, \\text{COVID}) \\over P(+ \\,|\\, \\text{No COVID})} = {90\\% \\over 1\\%} = 90 """, tex_to_color_map=t2c, ), ) for label, eq in zip(bf_labels, bf_equations): eq.scale(0.75) eq[-1].scale(2, about_edge=LEFT) eq.next_to(label, DOWN, buff=0.75) assumption_brace = Brace(bf_equations[0][-3:], DOWN) assumption_word = TextMobject("Assumption") assumption_word.next_to(assumption_brace, DOWN) self.play( Write(bf_labels[0]), FadeIn(bf_equations[0][:-3]), ) self.wait() self.play( GrowFromCenter(assumption_brace), FadeIn(assumption_word, shift=0.2 * DOWN), FadeIn(bf_equations[0][-3:]), ) self.wait() self.play(ShowCreationThenFadeOut(odds_underlines[0])) new_left_odds = get_odds_label(1, 1000)[1:] new_left_odds.move_to(odds_labels[0][1:], UL) approx = TexMobject("\\approx", font_size=72) approx.rotate(90 * DEGREES) approx.next_to(odds_labels[0][1:], DOWN, buff=0.5) self.play( FadeIn(approx, 0.25 * DOWN), odds_labels[0][1:].next_to, approx, DOWN, {"buff": 0.5}, FadeIn(new_left_odds, 0.5 * DOWN), odds_arrows[0].scale, 0.7, {"about_edge": RIGHT}, ) self.wait() curr_odds = new_left_odds.copy() self.play( curr_odds.move_to, odds_labels[1][1:], UR, path_arc=-30 * DEGREES ) self.play(ChangeDecimalToValue(curr_odds[0], 25)) self.wait() new_odds = curr_odds.copy() new_odds[0].set_value(1) new_odds[2].set_value(40) self.play( FadeOut(curr_odds, 0.5 * UP), FadeIn(new_odds, 0.5 * UP), ) curr_odds = new_odds self.wait() self.play( Write(bf_labels[1]), FadeIn(bf_equations[1]), ) self.wait() mid_odds = curr_odds.copy() self.add(mid_odds) self.play( curr_odds.move_to, odds_labels[2][1:], UR, path_arc=-30 * DEGREES ) self.play( ChangeDecimalToValue(curr_odds[0], 90) ) self.wait() new_odds = curr_odds.copy() new_odds[0].set_value(9) new_odds[2].set_value(4) self.play( FadeOut(curr_odds, 0.5 * UP), FadeIn(new_odds, 0.5 * UP), ) curr_odds = new_odds self.wait() # Posterior as a probability final_frac = TexMobject( "{{9} \\over {9} + {4}} \\approx 69.2\\%", tex_to_color_map={ "{9}": YELLOW, "{4}": GREY_B, } ) final_frac.next_to(odds_labels[2], DOWN, buff=0.4) self.play(FadeIn(final_frac, lag_ratio=0.2)) self.wait() # Get rid of population self.play( LaggedStartMap(FadeOut, pop_groups, shift=DOWN, lag_ratio=0.3), FadeOut(h_line), ) # Change symptom Bayes factor old_assumption = bf_equations[0][-3:] new_assumption = TexMobject("2") new_assumption.replace(old_assumption, 1) new_assumption.scale(0.8) new_assumption.set_color(BLUE) change_word = TextMobject("Change this", font_size=72) change_word.next_to(assumption_word, DOWN, buff=1.5) change_word.shift(LEFT) change_word.set_color(RED) change_arrow = Arrow(change_word.get_top(), assumption_word.get_bottom()) change_arrow.match_color(change_word) self.play( Write(change_word, run_time=1), GrowArrow(change_arrow), ) self.wait() self.play( FadeOut(old_assumption, shift=0.5 * UP), FadeIn(new_assumption, shift=0.5 * UP), assumption_brace.set_width, 1.5 * new_assumption.get_width(), {"stretch": True}, FadeOut(mid_odds), FadeOut(curr_odds), FadeOut(final_frac), ) self.wait() # New odds calculation curr_odds = new_left_odds.copy() self.play( curr_odds.move_to, mid_odds, UP, curr_odds.shift, SMALL_BUFF * RIGHT, FadeOut(odds_underlines[1]), path_arc=-30 * DEGREES, ) self.play(ChangeDecimalToValue(curr_odds[2], 500)) self.wait() mid_odds = curr_odds.copy() self.add(mid_odds) self.play( curr_odds.move_to, odds_labels[2][1:], UR, curr_odds.shift, 0.35 * RIGHT, FadeOut(odds_underlines[2]), path_arc=-30 * DEGREES, ) self.play(ChangeDecimalToValue(curr_odds[0], 90)) self.wait() new_odds = curr_odds.copy() new_odds[0].set_value(9) new_odds[2].set_value(50) new_odds.move_to(curr_odds, LEFT) self.play( FadeIn(new_odds, shift=0.5 * UP), FadeOut(curr_odds, shift=0.5 * UP), ) curr_odds = new_odds self.wait() # Prob calculation new_final_frac = TexMobject( "{{9} \\over {9} + {50}} \\approx 15.3\\%", tex_to_color_map={ "{9}": YELLOW, "{50}": GREY_B, } ) new_final_frac.replace(final_frac, 1) self.play(FadeIn(new_final_frac, lag_ratio=0.3)) self.wait() class ContrastTextbookAndRealWorld(Scene): def construct(self): pass class TwoMissteps(Scene): def construct(self): words = VGroup( TextMobject( "Misstep 1: ", "Fail to consider the prevalence." ), TextMobject( "Misstep 2: ", "Assume prior = prevalence" ), ) words[0][0].set_color(YELLOW) words[1][0].set_color(BLUE) words.scale(1.5) words.arrange(DOWN, buff=1, aligned_edge=LEFT) words.to_edge(LEFT) self.add(words) # Embed self.embed()