<jupyter_start><jupyter_text>I. Numpy  Импортируйте NumPy<jupyter_code>import numpy as np

#  Здесь и далее "print" используется для удоства, не везде закомментирован также умышленно.
# Так проще проверять самого себя<jupyter_output><empty_output><jupyter_text>Создайте одномерный массив размера 10, заполненный нулями и пятым элемент равным 1. Трансформируйте в двумерный массив.<jupyter_code># Т.к. элементов много, можно создать как руками, сразу предзаполнив 5й элемент единицей, так и автоматом.
# Показано оба способа
manual_arr = np.array([0, 0, 0, 0, 1, 0, 0, 0, 0, 0])
auto_arr = np.zeros(10)
auto_arr[4] = 1 #  Короткая запись замены по индексу
np.put(auto_arr, [4], [1]) #  Более длинная запись, годится когда массив сложный, в этом случае - будет читабельнее

# Трансформируйте в двумерный массив
# Не стал захламлять сразу в одну строку, но знаю как
auto_arr.resize((2, 5))
print(auto_arr)
print(auto_arr.shape)<jupyter_output>[[0. 0. 0. 0. 1.]
 [0. 0. 0. 0. 0.]]
(2, 5)<jupyter_text>Создайте одномерный массив со значениями от 10 до 49 и разверните его (первый элемент становится последним). Найдите в нем все четные элементы.<jupyter_code># Представлено пару способов.
ini_array1 = np.flipud(np.arange(start=10, stop=50))
ini_array2 = np.arange(start=10, stop=50)[::-1]
print(ini_array1)
print(ini_array2)<jupyter_output>[49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26
 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10]
[49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26
 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10]<jupyter_text>Создайте двумерный массив 3x3 со значениями от 0 до 8<jupyter_code>#Запись можно укоротить, что и сделал
arr2D = np.arange(9).reshape(3,3)
print(arr2D)<jupyter_output>[[0 1 2]
 [3 4 5]
 [6 7 8]]<jupyter_text>Создайте массив 4x3x2 со случайными значениями. Найти его минимум и максимум.<jupyter_code># Запись можно укоротить, но оставил в 2 шага в угоду читабельности
# Выбраны числа, так как даже рандомные числа работают быстрее чем любой другой тип данных
arr3D = np.random.randint(0, 100, size=(4, 3, 2))
print(arr3D)
# Вывел кортежем для удобства
min_max = arr3D.max(), arr3D.min()

print(min_max)<jupyter_output>[[[52 10]
  [88 20]
  [54 90]]

 [[87 95]
  [46 65]
  [17 91]]

 [[82 63]
  [ 0 48]
  [81 83]]

 [[24 43]
  [84 30]
  [16 32]]]
(95, 0)<jupyter_text>Создайте два двумерных массива размерами 6x4 и 4x3 и произведите их матричное умножение.<jupyter_code>#  Запись в последней строке такая более читабельна, видно множители и конкретный метод и сложнее ошибиться
arr1 = np.arange(24).reshape(6,4)
arr2 = np.arange(12).reshape(4,3)
print(np.dot(arr1, arr2))<jupyter_output>[[ 42  48  54]
 [114 136 158]
 [186 224 262]
 [258 312 366]
 [330 400 470]
 [402 488 574]]<jupyter_text>Создайте случайный двумерный массив 7x7, найти у него среднее и стандартное оклонение. Нормализуйте этот массив.<jupyter_code>rnd_arr = np.random.randint(0, 49, size=(7, 7))
print(rnd_arr)

#  Среднее отклонение
print(np.mean(rnd_arr))

# Стандартное отклонение
print(np.std(rnd_arr))

# Нормализованный массив, с использованием данных что получил выше (так быстрее) по формуле*
# * Для инфо:Нормализация осуществляется относительно среднего значения. 
# Вычисляется как отношение разности между исходным значением и средней выборки
# к среднему квадратическому отклонению. 
# У нормализованной матрицы средняя равна нулю (или очень близка ней),
# а среднее квадратическое отклонение равно 1 (или также очень близко к этому значению).
norms_rnd_arr = (rnd_arr - np.mean(rnd_arr) / np.std(rnd_arr))
print(norms_rnd_arr)<jupyter_output>[[45 17 17  0 21  7  9]
 [ 9  4 26  7 39  6 41]
 [17 48 19 39 39 45 28]
 [26  1  7 36  6 17 19]
 [44 28 36  7 46 47 33]
 [10  7 23 38 29 43 37]
 [46  5  3 46 40  1  3]]
23.816326530612244
15.965106683125605
[[43.50822628 15.50822628 15.50822628 -1.49177372 19.50822628  5.50822628
   7.50822628]
 [ 7.50822628  2.50822628 24.50822628  5.50822628 37.50822628  4.50822628
  39.50822628]
 [15.50822628 46.50822628 17.50822628 37.50822628 37.50822628 43.50822628
  26.50822628]
 [24.50822628 -0.49177372  5.50822628 34.50822628  4.50822628 15.50822628
  17.50822628]
 [42.50822628 26.50822628 34.50822628  5.50822628 44.50822628 45.50822628
  31.50822628]
 [ 8.50822628  5.50822628 21.50822628 36.50822628 27.50822628 41.50822628
  35.50822628]
 [44.50822628  3.50822628  1.50822628 44.50822628 38.50822628 -0.49177372
   1.50822628]]<jupyter_text>II. Pandas  Импортируйте: pandas, matplotlib, seaborn<jupyter_code>import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns<jupyter_output><empty_output><jupyter_text>Загрузите датасет Tips из набора датасетов seaborn<jupyter_code># Зададим сразу переменную
df = sns.load_dataset('tips')<jupyter_output><empty_output><jupyter_text>Посмотрите на первые 5 строчек<jupyter_code># Можно так
df[:5]
#А можно так
df.head()<jupyter_output><empty_output><jupyter_text>Узнайте сколько всего строчек и колонок в данных<jupyter_code>df.shape<jupyter_output><empty_output><jupyter_text>Проверьте есть ли пропуски в данных<jupyter_code># Пропусков нет
# Можно так -
df.isnull().sum()
# Еще короче запись
df.isna().sum()<jupyter_output><empty_output><jupyter_text>Посмотрите на распределение числовых признаков<jupyter_code># Через MatPlotLib
df.boxplot()

# Напрямую через Pandas
df.hist(legend = True)

# Через Seaborn. Совместил все признаки и поставил прозрачность на 50%
sns.histplot(df, alpha = 0.5);<jupyter_output><empty_output><jupyter_text>Найдите максимальное значение 'total_bill'<jupyter_code># Синтаксис упрощён
df['total_bill'].max()<jupyter_output><empty_output><jupyter_text>Найдите количество курящих людей<jupyter_code>df[['smoker']].eq('Yes').sum() # Интересно что данное поле не булёвое, поэтому поиск по строке, плохой датасет)<jupyter_output><empty_output><jupyter_text>Узнайте какой средний 'total_bill' в зависимости от 'day'<jupyter_code>df.groupby('day', as_index=False)['total_bill'].mean()<jupyter_output><empty_output><jupyter_text>Отберите строчки с 'total_bill' больше медианы и узнайте какой средний 'tip' в зависимости от 'sex'<jupyter_code># Немного запутанно написано, но вроде читабельно
df[df.total_bill> df.total_bill.mean()].groupby('sex', as_index=False)['tip'].mean()
#df.total_bill.mean()<jupyter_output><empty_output><jupyter_text>Преобразуйте признак 'smoker' в бинарный (0-No, 1-Yes)<jupyter_code># Задаем эквиваленты
bool_dt = {'Yes': True, 'No': False}
# Пробегаем
df['smoker'].map(bool_dt)<jupyter_output><empty_output><jupyter_text>III. Visualization  Постройте гистограмму распределения признака 'total_bill'<jupyter_code>sns.histplot(df['total_bill'], alpha = 1);<jupyter_output><empty_output><jupyter_text>Постройте scatterplot, представляющий взаимосвязь между признаками 'total_bill' и 'tip'<jupyter_code># Поменял цвет в угоду фону
df.plot.scatter(x = 'total_bill', y = 'tip', s = 10, c = 'g');<jupyter_output><empty_output><jupyter_text>Постройте pairplot<jupyter_code>sns.pairplot(df);<jupyter_output><empty_output><jupyter_text>Постройте график взаимосвязи между признаками 'total_bill' и 'day'<jupyter_code># Без Seaborn'а
df.plot.scatter(x = 'day', y = 'total_bill', c = 'total_bill', cmap = 'viridis');

# Вывел так, чтобы увидеть заодно зависимость и по размеру чаевых удобнее.
sns.jointplot(x = 'day', y = 'total_bill', data = df);<jupyter_output><empty_output><jupyter_text>Постройте две гистограммы распределения признака 'tip' в зависимости от категорий 'time'<jupyter_code># Вывел самым простым способом
sns.displot(df, x="tip", col="time");<jupyter_output><empty_output><jupyter_text>Постройте два графика scatterplot, представляющих взаимосвязь между признаками 'total_bill' и 'tip' один для Male, другой для Female и раскрасьте точки в зависимоти от признака 'smoker'<jupyter_code># Согласно офиц.документации (https://seaborn.pydata.org/generated/seaborn.scatterplot.html#seaborn.scatterplot)
# relplot безопаснее для сохранения семантической синхронизации между графиками чем  
# FacetGrid, показанный на занятии, поэтому использовал его. Да и синтаксис понятнее.
sns.relplot(
    data=df, x="total_bill", y="tip",
    col="sex", hue="smoker", 
    kind="scatter"
);<jupyter_output><empty_output><jupyter_text>Сделайте выводы по анализу датасета и построенным графикам. По желанию можете продолжить анализ данных и также отразить это в выводах.<jupyter_code>'''
Выводы:
1. Датасет годится как sampledata - нет пустых значений и слишком странных выбросов.
2. На выходных больше чаевых, на 15-20%
3. Средний размер чаевых при чеке выше медианы у мужчин и женщин практически одинаков.
4. Размер чаевых не растет линейно при выходе размера чека за медиану примерно (если не учитывать аномалии/выбросы) и остается на одном уровне примерно.
   как при чеке 30, так и выше (цифры условны)
5. Размер чаевых гораздо выше и т.д. в 17-18 часов, чем в 12 (ланч)
6. Курящие мужчины дают меньше чаевых по размеру по отношению к общему чеку, у курящих график более линейный, у женщин нет явной зависимости между этими параметрами.
'''<jupyter_output><empty_output>