From 07cda695bb5d5ace49abb55077fab613a3bc3e06 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 18 Feb 2020 22:43:23 -0800 Subject: [PATCH] Finally, some work on the beta video --- from_3b1b/active/bayes/beta.py | 240 ++++++++++++++++++++++++- from_3b1b/active/bayes/beta_helpers.py | 108 +++++++++++ 2 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 from_3b1b/active/bayes/beta_helpers.py diff --git a/from_3b1b/active/bayes/beta.py b/from_3b1b/active/bayes/beta.py index 143ca5b7..a92f54da 100644 --- a/from_3b1b/active/bayes/beta.py +++ b/from_3b1b/active/bayes/beta.py @@ -1,9 +1,10 @@ from manimlib.imports import * +from from_3b1b.active.bayes.beta_helpers import * OUTPUT_DIRECTORY = "bayes/beta" -class BarChart(Axes): +class Histogram(Axes): CONFIG = { "x_min": 0, "x_max": 10, @@ -72,7 +73,6 @@ class BarChart(Axes): pass # Bar manipulations - # Scenes @@ -81,3 +81,239 @@ class BarChartTest(Scene): bar_chart = BarChart() bar_chart.to_edge(DOWN) self.add(bar_chart) + + +class Thumbnail(Scene): + def construct(self): + t2c = { + "95\\%": BLUE, + "92\\%": YELLOW, + "50": BLUE_C, + "200": YELLOW, + } + kw = {"tex_to_color_map": t2c} + text = VGroup( + TextMobject("95\\% with 50 reviews", **kw), + TextMobject("vs.", **kw), + TextMobject("92\\% with 200 reviews", **kw), + ) + text.scale(2) + text.arrange(DOWN, buff=LARGE_BUFF) + text.set_width(FRAME_WIDTH - 1) + self.add(text) + + self.embed() + + +class AskAboutUnknownProbabilities(Scene): + def construct(self): + # Setup + unknown_title, prob_title = titles = self.get_titles() + + v_line = Line(UP, DOWN) + v_line.set_height(FRAME_HEIGHT) + v_line.set_stroke([WHITE, LIGHT_GREY], 3) + h_line = Line(LEFT, RIGHT) + h_line.set_width(FRAME_WIDTH) + h_line.next_to(titles, DOWN) + + processes = VGroup( + get_random_coin(shuffle_time=1), + get_random_die(shuffle_time=1.20), + get_random_card(shuffle_time=2), + ) + processes.arrange(DOWN, buff=0.7) + processes.next_to(unknown_title, DOWN, LARGE_BUFF) + processes_rect = BackgroundRectangle(processes) + processes_rect.set_fill(BLACK, 1) + + prob_labels = VGroup( + TexMobject("P(", "00", ")", "=", "1 / 2"), + TexMobject("P(", "00", ")", "=", "1 / 6}"), + TexMobject("P(", "00", ")", "=", "1 / 52}"), + ) + prob_labels.scale(1.5) + prob_labels.arrange(DOWN, aligned_edge=LEFT) + prob_labels.match_x(prob_title) + for pl, pr in zip(prob_labels, processes): + pl.match_y(pr) + content = pr[1].copy() + content.replace(pl[1], dim_to_match=0) + pl.submobjects[1] = content + + # Putting numbers to the unknown + number_rects = VGroup(*[ + SurroundingRectangle(pl[-1]) + for pl in prob_labels + ]) + number_rects.set_stroke(YELLOW, 2) + + for pl in prob_labels: + pl.save_state() + pl[:3].match_x(prob_title) + pl[3:].match_x(prob_title) + pl.set_opacity(0) + + self.add(processes) + self.play( + LaggedStartMap(FadeInFromDown, titles), + LaggedStart( + ShowCreation(v_line), + ShowCreation(h_line), + ), + run_time=1 + ) + self.wait(2) + self.play( + LaggedStartMap(Restore, prob_labels), + run_time=3, + lag_ratio=0.3, + ) + self.play( + LaggedStartMap( + ShowCreationThenFadeOut, + number_rects, + run_time=3, + ) + ) + self.wait(2) + + # Highlight coin flip + fade_rect = BackgroundRectangle( + VGroup(prob_labels[1:], processes[1:]), + buff=MED_SMALL_BUFF, + ) + fade_rect.set_width(FRAME_WIDTH, stretch=True) + fade_rect.set_fill(BLACK, 0.8) + + prob_half = prob_labels[0] + half = prob_half[-1] + half_underline = Line(LEFT, RIGHT) + half_underline.set_width(half.get_width() + MED_SMALL_BUFF) + half_underline.next_to(half, DOWN, buff=SMALL_BUFF) + half_underline.set_stroke(YELLOW, 3) + + self.add(fade_rect, v_line, prob_half) + self.play(FadeIn(fade_rect)) + self.wait(2) + self.play( + ShowCreation(half_underline), + half.set_color, YELLOW, + ) + self.play(FadeOut(half_underline)) + self.wait(4) + + # Transition to question + processes.suspend_updating() + self.play( + LaggedStart( + FadeOutAndShift(unknown_title, UP), + FadeOutAndShift(prob_title, UP), + lag_ratio=0.2, + ), + FadeOutAndShift(h_line, UP, lag_ratio=0.1), + FadeOutAndShift(processes, LEFT, lag_ratio=0.1), + FadeOut(prob_labels[1]), + FadeOut(prob_labels[2]), + ApplyMethod(fade_rect.set_fill, BLACK, 1, remover=True), + v_line.rotate, 90 * DEGREES, + v_line.shift, 0.6 * FRAME_HEIGHT * UP, + prob_half.center, + prob_half.to_edge, UP, + run_time=2, + ) + self.clear() + self.add(prob_half) + + arrow = Vector(UP) + arrow.next_to(half, DOWN) + question = TextMobject("What exactly does\\\\this mean?") + question.next_to(arrow, DOWN) + + self.play( + GrowArrow(arrow), + FadeInFrom(question, UP), + ) + self.wait(2) + self.play( + FadeOutAndShift(question, RIGHT), + Rotate(arrow, 90 * DEGREES), + VFadeOut(arrow), + ) + + # Show long run averages + self.show_many_coins(20, 50) + self.show_many_coins(40, 100) + + # Make probability itself unknown + q_marks = TexMobject("???") + q_marks.set_color(YELLOW) + q_marks.replace(half, dim_to_match=0) + + randy = Randolph(mode="confused") + randy.center() + randy.look_at(prob_half) + + self.play( + FadeOutAndShift(half, UP), + FadeInFrom(q_marks, DOWN), + ) + self.play(FadeIn(randy)) + self.play(Blink(randy)) + self.wait() + + # self.embed() + + def get_titles(self): + unknown_label = TextMobject("Unknown event") + prob_label = TextMobject("Probability") + titles = VGroup(unknown_label, prob_label) + titles.scale(1.5) + + unknown_label.move_to(FRAME_WIDTH * LEFT / 4) + prob_label.move_to(FRAME_WIDTH * RIGHT / 4) + titles.to_edge(UP, buff=MED_SMALL_BUFF) + titles.set_color(BLUE) + return titles + + def show_many_coins(self, n_rows, n_cols): + coin_choices = VGroup( + get_coin(BLUE_E, "H"), + get_coin(RED_E, "T"), + ) + coin_choices.set_stroke(width=0) + coins = VGroup(*[ + random.choice(coin_choices).copy() + for x in range(n_rows * n_cols) + ]) + + def organize_coins(coin_group): + coin_group.scale(1 / coin_group[0].get_height()) + coin_group.arrange_in_grid(n_rows=n_rows) + coin_group.set_width(FRAME_WIDTH - 1) + coin_group.to_edge(DOWN, MED_LARGE_BUFF) + + organize_coins(coins) + + sorted_coins = VGroup() + for coin in coins: + coin.generate_target() + sorted_coins.add(coin.target) + sorted_coins.submobjects.sort(key=lambda m: m.symbol) + organize_coins(sorted_coins) + + self.play(LaggedStartMap( + FadeInFrom, coins, + lambda m: (m, 0.2 * DOWN), + run_time=3, + rate_func=linear + )) + self.wait() + self.play(LaggedStartMap( + MoveToTarget, coins, + path_arc=30 * DEGREES, + run_time=2, + lag_ratio=1 / len(coins), + )) + self.wait() + self.play(FadeOut(coins)) diff --git a/from_3b1b/active/bayes/beta_helpers.py b/from_3b1b/active/bayes/beta_helpers.py new file mode 100644 index 00000000..b1d1f6d9 --- /dev/null +++ b/from_3b1b/active/bayes/beta_helpers.py @@ -0,0 +1,108 @@ +from manimlib.imports import * + + +# Images of randomness + +def get_random_process(choices, shuffle_time=2, total_time=4, change_rate=0.05, + h_buff=0.05, v_buff=0.05): + content = choices[0] + + container = Square() + container.set_opacity(0) + container.set_width(content.get_width() + 2 * h_buff, stretch=True) + container.set_height(content.get_height() + 2 * v_buff, stretch=True) + container.move_to(content) + container.add(content) + container.time = 0 + container.last_change_time = 0 + + def update(container, dt): + container.time += dt + time = container.time + change = all([ + (time % total_time) < shuffle_time, + (time - container.last_change_time) > change_rate, + ]) + if change: + mob = container.submobjects[0] + new_mob = random.choice(choices) + new_mob.match_height(mob) + new_mob.move_to(container, DL) + new_mob.shift(2 * np.random.random() * h_buff * RIGHT) + new_mob.shift(2 * np.random.random() * v_buff * UP) + container.submobjects = [new_mob] + container.last_change_time = time + + container.add_updater(update) + return container + + +def get_coin(color, symbol): + coin = VGroup() + circ = Circle() + circ.set_fill(color, 1) + circ.set_stroke(WHITE, 2) + circ.set_height(1) + label = TextMobject(symbol) + label.set_height(0.5 * circ.get_height()) + label.move_to(circ) + coin.add(circ, label) + coin.symbol = symbol + coin.lock_triangulation() + return coin + + +def get_random_coin(**kwargs): + coins = VGroup( + get_coin(BLUE_E, "H"), + get_coin(RED_E, "T"), + ) + return get_random_process(coins, **kwargs) + + +def get_die_faces(): + dot = Dot() + dot.set_width(0.15) + dot.set_color(BLUE_B) + + square = Square() + square.round_corners(0.25) + square.set_stroke(WHITE, 2) + square.set_fill(DARKER_GREY, 1) + square.set_width(0.6) + + edge_groups = [ + (ORIGIN,), + (UL, DR), + (UL, ORIGIN, DR), + (UL, UR, DL, DR), + (UL, UR, ORIGIN, DL, DR), + (UL, UR, LEFT, RIGHT, DL, DR), + ] + + arrangements = VGroup(*[ + VGroup(*[ + dot.copy().move_to(square.get_bounding_box_point(ec)) + for ec in edge_group + ]) + for edge_group in edge_groups + ]) + square.set_width(1) + + faces = VGroup(*[ + VGroup(square.copy(), arrangement) + for arrangement in arrangements + ]) + faces.arrange(RIGHT) + + return faces + + +def get_random_die(**kwargs): + return get_random_process(get_die_faces(), **kwargs) + + +def get_random_card(height=1, **kwargs): + cards = DeckOfCards() + cards.set_height(height) + return get_random_process(cards, **kwargs)