def inverse_normal_cdf(p: float,
                       mu: float = 0,
                       sigma: float = 1,
                       tolerance: float = 0.00001) -> float:
    """Find approximate inverse using binary search"""

    # if not standard, compute standard and rescale
    if mu != 0 or sigma != 1:
        return mu + sigma * inverse_normal_cdf(p, tolerance=tolerance)

    low_z = -10.0                      # normal_cdf(-10) is (very close to) 0
    hi_z  =  10.0                      # normal_cdf(10)  is (very close to) 1
    while hi_z - low_z > tolerance:
        mid_z = (low_z + hi_z) / 2     # Consider the midpoint
        mid_p = normal_cdf(mid_z)      # and the cdf's value there
        if mid_p < p:
            low_z = mid_z              # Midpoint too low, search above it
        else:
            hi_z = mid_z               # Midpoint too high, search below it

    return mid_z def add(v: Vector, w: Vector) -> Vector:
def subtract(v: Vector, w: Vector) -> Vector:
def vector_sum(vectors: List[Vector]) -> Vector:
def scalar_multiply(c: float, v: Vector) -> Vector:
def vector_mean(vectors: List[Vector]) -> Vector:
def dot(v: Vector, w: Vector) -> float:
def sum_of_squares(v: Vector) -> float:
def magnitude(v: Vector) -> float:
def squared_distance(v: Vector, w: Vector) -> float:
def distance(v: Vector, w: Vector) -> float:
def distance(v: Vector, w: Vector) -> float:  # type: ignore
def shape(A: Matrix) -> Tuple[int, int]:
def get_row(A: Matrix, i: int) -> Vector:
def get_column(A: Matrix, j: int) -> Vector:
def make_matrix(num_rows: int,
                num_cols: int,
                entry_fn: Callable[[int, int], float]) -> Matrix:
def identity_matrix(n: int) -> Matrix:
A = [[1, 2, 3],  # A has 2 rows and 3 columns
     [4, 5, 6]]
B = [[1, 2],     # B has 3 rows and 2 columns
     [3, 4],
     [5, 6]] def distance(v: Vector, w: Vector) -> float:
    """Computes the distance between v and w"""
    return math.sqrt(squared_distance(v, w)) def vector_mean(vectors: List[Vector]) -> Vector:
    """Computes the element-wise average"""
    n = len(vectors)
    return scalar_multiply(1/n, vector_sum(vectors)) def subtract(v: Vector, w: Vector) -> Vector:
    """Subtracts corresponding elements"""
    assert len(v) == len(w), "vectors must be the same length"

    return [v_i - w_i for v_i, w_i in zip(v, w)] def magnitude(v: Vector) -> float:
    """Returns the magnitude (or length) of v"""
    return math.sqrt(sum_of_squares(v))   # math.sqrt is square root function def dot(v: Vector, w: Vector) -> float:
    """Computes v_1 * w_1 + ... + v_n * w_n"""
    assert len(v) == len(w), "vectors must be same length"

    return sum(v_i * w_i for v_i, w_i in zip(v, w)) def gradient_step(v: Vector, gradient: Vector, step_size: float) -> Vector:
    """Moves `step_size` in the `gradient` direction from `v`"""
    assert len(v) == len(gradient)
    step = scalar_multiply(step_size, gradient)
    return add(v, step) def scalar_multiply(c: float, v: Vector) -> Vector:
    """Multiplies every element by c"""
    return [c * v_i for v_i in v] def subtract(v: Vector, w: Vector) -> Vector:
    """Subtracts corresponding elements"""
    assert len(v) == len(w), "vectors must be the same length"

    return [v_i - w_i for v_i, w_i in zip(v, w)]
from typing import List, Dict
from collections import Counter
from scratch.probability import inverse_normal_cdf
from scratch.statistics import correlation
from scratch.linear_algebra import Matrix, Vector, make_matrix
from typing import NamedTuple
from dateutil.parser import parse
from typing import Optional
from dateutil.parser import parse
from collections import defaultdict
from typing import List
from collections import defaultdict
from scratch.linear_algebra import distance
from typing import Tuple
from scratch.linear_algebra import vector_mean
from scratch.statistics import standard_deviation
from scratch.linear_algebra import subtract
from scratch.linear_algebra import magnitude
from scratch.linear_algebra import dot
from scratch.gradient_descent import gradient_step
from scratch.linear_algebra import scalar_multiply
from scratch.linear_algebra import subtract
    from scratch.probability import inverse_normal_cdf
    from scratch.statistics import correlation
    from typing import List
    from typing import List
import math
import matplotlib.pyplot as plt
import random
import datetime
import re
import csv
import tqdm
    import random
    import csv


def bucketize(point: float, bucket_size: float) -> float:
    """Floor the point to the next lower multiple of bucket_size"""
    return bucket_size * math.floor(point / bucket_size)

def make_histogram(points: List[float], bucket_size: float) -> Dict[float, int]:
    """Buckets the points and counts how many in each bucket"""
    return Counter(bucketize(point, bucket_size) for point in points)

def plot_histogram(points: List[float], bucket_size: float, title: str = ""):
    histogram = make_histogram(points, bucket_size)
    plt.bar(histogram.keys(), histogram.values(), width=bucket_size)
    plt.title(title)



def random_normal() -> float:
    """Returns a random draw from a standard normal distribution"""
    return inverse_normal_cdf(random.random())

xs = [random_normal() for _ in range(1000)]
ys1 = [ x + random_normal() / 2 for x in xs]
ys2 = [-x + random_normal() / 2 for x in xs]

plt.scatter(xs, ys1, marker='.', color='black', label='ys1')
plt.scatter(xs, ys2, marker='.', color='gray',  label='ys2')
plt.xlabel('xs')
plt.ylabel('ys')
plt.legend(loc=9)
plt.title("Very Different Joint Distributions")
# plt.show()


plt.savefig('im/working_scatter.png')
plt.gca().clear()




assert 0.89 < correlation(xs, ys1) < 0.91
assert -0.91 < correlation(xs, ys2) < -0.89



def correlation_matrix(data: List[Vector]) -> Matrix: