#   Тема “Визуализация данных в Matplotlib”

#   Задание 1
#   Загрузите модуль pyplot библиотеки matplotlib с псевдонимом plt, а также библиотеку numpy с псевдонимом np.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

#   Примените магическую функцию %matplotlib inline для отображения графиков в Jupyter Notebook
# %matplotlib inline
#   и настройки конфигурации ноутбука со значением 'svg' для более четкого отображения графиков.
# %config InlineBackend.figure_format = 'svg'
#   Создайте список под названием x с числами 1, 2, 3, 4, 5, 6, 7
x = [1, 2, 3, 4, 5, 6, 7]
#   и список y с числами 3.5, 3.8, 4.2, 4.5, 5, 5.5, 7.
y = [3.5, 3.8, 4.2, 4.5, 5, 5.5, 7]


class GRAPH1:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    #   С помощью функции plot постройте график,
    #   соединяющий линиями точки с горизонтальными координатами из списка x и вертикальными - из списка y.
    def line(self):
        plt.plot(self.x, self.y)

    #   Затем в следующей ячейке постройте диаграмму рассеяния (другие названия - диаграмма разброса, scatter plot).
    def dots(self):
        plt.scatter(self.x, self.y)


graph1 = GRAPH1(x, y)

#   Задание 2
#   С помощью функции linspace из библиотеки Numpy создайте массив t из 51 числа от 0 до 10 включительно.
t = np.linspace(0, 10, 51)
#   Создайте массив Numpy под названием f, содержащий косинусы элементов массива t.
f = np.cos(t)


class GRAPH2:
    def __init__(self, t, f):
        self.t = t
        self.f = f

    #   Постройте линейную диаграмму, используя массив t для координат по горизонтали,
    #   а массив f - для координат по вертикали. Линия графика должна быть зеленого цвета.
    def cos(self):
        plt.plot(self.t, self.f, c='green', label='cos')

    #   Выведите название диаграммы - 'График f(t)'.
    def title(self):
        self.cos()
        title_dict = {'fontsize': 20, 'fontweight': 'bold', 'color': 'grey', 'family': 'Calibri'}
        plt.title('График f(t)', fontdict=title_dict)

    #   Также добавьте названия для горизонтальной оси - 'Значения t' и для вертикальной - 'Значения f'.
    def labels(self):
        self.title()
        label_dict = {'fontsize': 14, 'color': 'grey', 'family': 'Calibri'}
        plt.xlabel('Значения t', fontdict=label_dict)
        plt.ylabel('Значения f', fontdict=label_dict)

    #   Ограничьте график по оси x значениями 0.5 и 9.5, а по оси y - значениями -2.5 и 2.5.
    def axis(self):
        self.labels()
        plt.axis([0.5, 9.5, -2.5, 2.5])

    #   ДОПОЛНИТЕЛЬНО
    def addition(self):
        self.axis()
        legend_dict = {'size': 10, 'family': 'Calibri'}
        legend = plt.legend(loc='lower right', prop=legend_dict, frameon=False)
        plt.setp(legend.get_texts(), color='grey')
        plt.grid(c='lightgrey')


graph2 = GRAPH2(t, f)

#   *Задание 3
#   С помощью функции linspace библиотеки Numpy создайте массив x из 51 числа от -3 до 3 включительно.
x = np.linspace(-3, 3, 51)
#   Создайте массивы y1, y2, y3, y4 по следующим формулам:
y1 = x ** 2
y2 = 2 * x + 0.5
y3 = -3 * x - 1.5
y4 = np.sin(x)


def graph3(x, y):
    #   Используя функцию subplots модуля matplotlib.pyplot, создайте объект matplotlib.figure.
    #   Figure с названием fig и массив объектов Axes под названием ax,
    #   причем так, чтобы у вас было 4 отдельных графика в сетке, состоящей из двух строк и двух столбцов.
    fig, ax = plt.subplots(nrows=2, ncols=2)
    ax1, ax2, ax3, ax4 = ax.flatten()
    #   В каждом графике массив x используется для координат по горизонтали.
    #   В левом верхнем графике для координат по вертикали используйте y1,
    ax1.plot(x, y1)
    #   в правом верхнем - y2,
    ax2.plot(x, y2)
    #   в левом нижнем - y3,
    ax3.plot(x, y3)
    #   в правом нижнем - y4.
    ax4.plot(x, y4)
    #   Дайте название графикам: 'График y1', 'График y2' и т.д.
    ax1.set_title('График y1')
    ax2.set_title('График y2')
    ax3.set_title('График y3')
    ax4.set_title('График y4')
    #   Для графика в левом верхнем углу установите границы по оси x от -5 до 5.
    ax1.set_xlim([-5, 5])
    #   Установите размеры фигуры 8 дюймов по горизонтали и 6 дюймов по вертикали.
    fig.set_size_inches(8, 6)
    #   Вертикальные и горизонтальные зазоры между графиками должны составлять 0.3.
    plt.subplots_adjust(wspace=0.3, hspace=0.3)


#   *Задание 4
#   В этом задании мы будем работать с датасетом, в котором приведены данные по мошенничеству с кредитными данными:
#   Credit Card Fraud Detection (информация об авторах:
#   Andrea Dal Pozzolo, Olivier Caelen, Reid A. Johnson and Gianluca Bontempi.
#   Calibrating Probability with Undersampling for Unbalanced
#   Classification. In Symposium on Computational Intelligence and Data Mining (CIDM), IEEE, 2015).
#   Ознакомьтесь с описанием и скачайте датасет creditcard.csv с сайта Kaggle.com по ссылке:
#   https://www.kaggle.com/mlg-ulb/creditcardfraud
#   Данный датасет является примером несбалансированных данных,
#   так как мошеннические операции с картами встречаются реже обычных.
#   Импортруйте библиотеку Pandas, а также используйте для графиков стиль “fivethirtyeight”.
#   Посчитайте с помощью метода value_counts количество наблюдений для каждого значения целевой переменной Class
plt.style.use('fivethirtyeight')
df = pd.read_csv('creditcard.csv')
class_count = df['Class'].value_counts()


#   и примените к полученным данным метод plot, чтобы построить столбчатую диаграмму.
def bar():
    class_count.plot(kind='bar')


#   Затем постройте такую же диаграмму, используя логарифмический масштаб.
def bar_log():
    class_count.plot(kind='bar', logy=True)


def hists():
    #   На следующем графике постройте две гистограммы по значениям признака V1
    #   - одну для мошеннических транзакций (Class равен 1)
    v1_class1 = df.set_index('Class')['V1'].filter(like='1')
    plt.hist(
        v1_class1,
        density=True,  # Подберите значение аргумента density так,
        # чтобы по вертикали графика было расположено не число наблюдений, а плотность распределения.
        bins=20,  # Число бинов должно равняться 20 для обеих гистограмм,
        alpha=0.5,  # а коэффициент alpha сделайте равным 0.5,
        # чтобы гистограммы были полупрозрачными и не загораживали друг друга.
        color='red'  # а мошеннических - красного.
    )
    #   и другую - для обычных (Class равен 0).
    v0_class0 = df.set_index('Class')['V1'].filter(like='0')
    plt.hist(
        v0_class0,
        density=True,
        bins=20,
        alpha=0.5,
        color='grey'  # Гистограмма обычных транзакций должна быть серого цвета,
    )
    #   Создайте легенду с двумя значениями: “Class 0” и “Class 1”.
    plt.legend(labels=['Class 1', 'Class 0'])
    #   Горизонтальной оси дайте название “Class”.
    plt.xlabel('Class')


#   **Задание на повторение материала
#   1.  Создать одномерный массив Numpy под названием a из 12 последовательных целых чисел
#   чисел от 12 до 24 невключительно
a = np.arange(12, 24)
print(f'a = {a}')

#   2.  Создать 5 двумерных массивов разной формы из массива a. Не использовать в аргументах метода reshape число -1.
a1 = a.reshape(2, 6)
print(f'a1 = {a1}')
a2 = a.reshape(3, 4)
print(f'a2 = {a2}')
a3 = a.reshape(4, 3)
print(f'a3 = {a3}')
a4 = a.reshape(6, 2)
print(f'a4 = {a4}')
a5 = a.reshape(12, 1)
print(f'a5 = {a5}')

#   3.  Создать 5 двумерных массивов разной формы из массива a. Использовать в аргументах метода reshape число -1
#   (в трех примерах - для обозначения числа столбцов, в двух - для строк).
a6 = a.reshape(-1, 6)
print(f'a6 = {a6}')
a7 = a.reshape(-1, 4)
print(f'a7 = {a7}')
a8 = a.reshape(-1, 3)
print(f'a8 = {a8}')
a9 = a.reshape(6, -1)
print(f'a9 = {a9}')
a10 = a.reshape(12, -1)
print(f'a10 = {a10}')

#   4.  Можно ли массив Numpy, состоящий из одного столбца и 12 строк, назвать одномерным?
# Ответ: Нет, так как внитри массива из 12 строк каждое число взято в скобки,
# что говорит нам о том, что их мерность изменена, даже не смотря та то, что в каждой строке по 1й цифре
# - это двумерный массив

#   5.  Создать массив из 3 строк и 4 столбцов, состоящий из случайных чисел с плавающей запятой
#   из нормального распределения со средним, равным 0 и среднеквадратичным отклонением, равным 1.0.
b = np.random.randn(3, 4)
print(f'b = {b}')
#   Получить из этого массива одномерный массив с таким же атрибутом size, как и исходный массив.
b1 = b.flatten()
print(f'b1 = {b1}')

#   6.  Создать массив a, состоящий из целых чисел, убывающих от 20 до 0 невключительно с интервалом 2.
a = np.arange(20, 0, -2)
print(f'a = {a}')

#   7.  Создать массив b, состоящий из 1 строки и 10 столбцов:
#   целых чисел, убывающих от 20 до 1 невключительно с интервалом 2.
b = np.arange(20, 0, -2).reshape(1, 10)
print(f'b = {b}')
#   В чем разница между массивами a и b?
# Ответ: Массив a - отдномерный, массив b - двумерный, так как при его создании мы указали,
# что он состоит не только из строк, но и из столбцов.

#   8.  Вертикально соединить массивы a и b. a - двумерный массив из нулей, число строк которого больше 1 и на 1 меньше,
#   чем число строк двумерного массива b, состоящего из единиц.
a = np.zeros((2, 2))
print(f'a = {a}')
b = np.ones((3, 2))
print(f'b = {b}')
v = np.vstack((a, b))
print(f'v = {v}')
#   Итоговый массив v должен иметь атрибут size, равный 10.
print(v.size)

#   9.  Создать одномерный массив а, состоящий из последовательности целых чисел от 0 до 12.
a = np.arange(0, 12)
print(f'a = {a}')
#   Поменять форму этого массива, чтобы получилась матрица A (двумерный массив Numpy),
#   состоящая из 4 строк и 3 столбцов.
A = a.reshape(4, 3)
print(f'A = {A}')
#   Получить матрицу At путем транспонирования матрицы A.
At = A.T
print(f'At = {At}')
#   Получить матрицу B, умножив матрицу A на матрицу At с помощью матричного умножения.
B = np.dot(A, At)
print(f'B = {B}')
#   Какой размер имеет матрица B?
print(B.shape)
#   Получится ли вычислить обратную матрицу для матрицы B и почему?
# B_inv = np.linalg.inv(B)
# Ответ: Не получится, тат как обратная матрица становится сингулярной

#   10. Инициализируйте генератор случайных числе с помощью объекта seed, равного 42.
np.random.seed(42)

#   11. Создайте одномерный массив c,
#   составленный из последовательности 16-ти случайных равномерно распределенных целых чисел от 0 до 16 невключительно.
c = np.random.randint(0, 16, 16)
print(f'c = {c}')

#   12. Поменяйте его форму так, чтобы получилась квадратная матрица C.
C = c.reshape(4, 4)
print(f'C = {C}')
#   Получите матрицу D, поэлементно прибавив матрицу B из предыдущего вопроса к матрице C, умноженной на 10.
D = np.add(B, (np.dot(C, 10)))
print(f'D = {D}')
#   Вычислите определитель,
print(f'Опрежелитель D: {np.linalg.det(D)}')
#   ранг
print(f'Ранг D: {np.linalg.matrix_rank(D)}')
#   и обратную матрицу D_inv для D.
D_inv = np.linalg.inv(D)
print(f'Обратная матрица D_inv:\n{D_inv}')

#   13. Приравняйте к нулю отрицательные числа в матрице D_inv, а положительные - к единице.
D_inv = np.where(D_inv < 0, 0, 1)
#   Убедитесь, что в матрице D_inv остались только нули и единицы.
print(F'Двоичная матрица D_inv:\n{D_inv}')
#   С помощью функции numpy.where, используя матрицу D_inv в качестве маски,
#   а матрицы B и C - в качестве источников данных, получите матрицу E размером 4x4.
#   Элементы матрицы E, для которых соответствующий элемент матрицы D_inv равен 1,
#   должны быть равны соответствующему элементу матрицы B, а элементы матрицы E,
#   для которых соответствующий элемент матрицы D_inv равен 0, должны быть равны соответствующему элементу матрицы C.
E = np.where(D_inv, B, C)
print(E)
