Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
Перевод публикуется с сокращениями, автор оригинальной статьи Shane Gary.

Эту связку полезно применять для быстрой визуализации одной таблицы с несколькими различными графиками, а также для создания надежного сайта на Django и Chart.js.
Почему Pandas? Все задачи можно выполнить изнутри Django непосредственно из БД, т. к. правильные запросы к базе всегда будут выгоднее для продакшена.
Почему Django? Это отличный выбор для проекта, где необходимо экспортировать данные в одну таблицу. Использование Flask будет более выигрышным вариантом, если не нужна аутентификация и доступ к данным из существующей базы.
Почему Chart.js? Если вы хотите развернуть кучу различных интерактивных диаграмм, изменив несколько переменных – Chart.js сделает это быстрее других.
Исходные тексты и простой набор данных из туториала вы найдете по ссылкам.
Настройка Django
Все действия в этом проекте будут происходить на сервере с Debian.
pip install django pandas
django-admin startproject django_charts
cd django_charts
python manage.py migrate
python manage.py createsuperuser
python manage.py startapp data
cd data
mkdir templates
cd ..
python manage.py runserver
Дополнительно можете установить себе palettable – цветовую палитру для Python, однако код будет работать и без нее.
pip install palettable
На основе набора данных мы создадим следующую модель, но она должна быть изменена под ваши нужды.
from django.db import models
class Purchase(models.Model):
city = models.CharField(max_length=50)
customer_type = models.CharField(max_length=50)
gender = models.CharField(max_length=50)
unit_price = models.FloatField()
quantity = models.IntegerField()
product_line = models.CharField(max_length=50)
tax = models.FloatField()
total = models.FloatField()
date = models.DateField()
time = models.TimeField()
payment = models.CharField(max_length=50)
cogs = models.FloatField()
profit = models.FloatField()
rating = models.FloatField()
Обязательно обновите БД после ее создания:
python manage.py makemigrations
python manage.py migrate
С помощью Pandas и Django сделайте загрузку csv в базу данных для Kaggle:
import pandas as pd
from .models import Purchase
# dataset from https://www.kaggle.com/aungpyaeap/supermarket-sales
# headers changed and invoice number col removed
def csv_to_db():
df = pd.read_csv('supermarket_sales.csv') # use pandas to read the csv
records = df.to_records() # convert to records
# loop through and create a purchase object using django
for record in records:
purchase = Purchase(
city=record[3],
customer_type=record[4],
gender=record[5],
product_line=record[6],
unit_price=record[7],
quantity=record[8],
tax=record[9],
total=record[10],
date=datetime.strptime(record[11], '%m/%d/%Y').date(),
time=record[12],
payment=record[13],
cogs=record[14],
profit=record[16],
rating=record[17],
)
purchase.save()
Импортируйте себе всю эту штуку:
from django.views.generic import TemplateView
from .methods import csv_to_db
class Dashboard(TemplateView):
template_name = 'dashboard.html'
def get_context_data(self, **kwargs):
# get the data from the default method
context = super().get_context_data(**kwargs)
csv_to_db()
Затем создайте пустой файл base/dashboard.html и data/urls.py:
from django.urls import path
from data import views
urlpatterns = [
path('', views.Dashboard.as_view(), name='dashboard')
]
Отредактируйте django_charts/urls.py, чтобы добавить URL для данных:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('data/', include('data.urls')),
]
Обновите список django_charts/settings.py, чтобы включить data в INSTALLED_APPS:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'data',
]
Убедимся, что можем видеть покупку в админке, добавив это в data/admin.py:
from django.contrib import admin
from .models import Purchase
admin.site.register(Purchase)
Теперь вы можете перейти на страницу дашборда (127.0.0.1:8000/data/), а потом проверить в админке (127.0.0.1:8000/admin/data/purchase/), видны ли все записи. Если вы используете тот же набор данных, их должно быть около 1000.
Настройка HTML
Есть базовый файл, который мы будем расширять:
{% load static %}
<!doctype html>
<html lang="en">
<head>
<title>
{% block title %}{% endblock %}
</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="shortcut icon" type="image/x-icon" href="{% static 'img/favicon.png' %}">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
{% block page_content %}{% endblock %}
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script
src="http://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous">
</script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous">
</script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous">
</script>
<!-- Chart.JS -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3"></script>
<script>
$(document).ready(function(){
{% block js_scripts %}{% endblock %}
})
</script>
</body>
</html>
Немного модернизируем его:
{% block title %}{% endblock %}
{% block custom_css %}{% endblock %}
{% block page_content %}{% endblock %}
{% block js_scripts %}{% endblock %}
Это даст возможность добавлять блоки с любой страницы. Обратите внимание, что здесь включен bootstrap CDN, чтобы использовать popper.js (это опционально). Можно выполнить проект совсем без bootstrap, но придется отредактировать dashboard.html.
Все написанные сценарии обернуты в $(document).ready
, чтобы не
происходило никаких манипуляций до тех пор, пока страница не будет готова.
Далее рассмотрим страницу с графиками – dashboard.html:
{% extends 'base.html' %}
{% block page_content %}
<div class="container">
<div class="row mb-4 mt-4">
<div class="col"
<div class="card-deck">
{% for chart in charts %}
<div class="card">
<div class="card-body">
<div class="chart-container" style="height:150; width:150">
{{ chart.html|safe }}
</div>
</div>
</div>
{% if forloop.counter|divisibleby:2 %}
<div class="w-100 d-none d-sm-block d-md-none mb-4"><!-- wrap every 2 on sm--></div>
{% endif %}
{% if forloop.counter|divisibleby:3 %}
<div class="w-100 d-none d-md-block d-lg-none mb-4"><!-- wrap every 3 on md--></div>
{% endif %}
{% if forloop.counter|divisibleby:4 %}
<div class="w-100 d-none d-lg-block d-xl-none mb-4"><!-- wrap every 4 on lg--></div>
{% endif %}
{% if forloop.counter|divisibleby:5 %}
<div class="w-100 d-none d-xl-block mb-4"><!-- wrap every 5 on xl--></div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block js_scripts %}
{% for chart in charts %}
{{ chart.js|safe }}
{% endfor %}
{% endblock %}
Здесь создается контейнер, строка и колонка, внутри которой лежит «колода карт», создающая карты одинакового размера.
Колода карт будет пытаться втиснуть все в одну строку. Счетчики forloop.counters используются для определения размера экрана и соответствующего обертывания колоды карт, однако на практике вы обнаружите, что Chart.js зачастую игнорирует настройки размера холста.
views.py
Чтобы лучше понять происходящее, посмотрим на views.py:
import pandas as pd
import numpy as np
from django.views.generic import TemplateView
from .methods import csv_to_db
from .models import Purchase
from .charts import objects_to_df, Chart
PALETTE = ['#465b65', '#184c9c', '#d33035', '#ffc107', '#28a745', '#6f7f8c', '#6610f2', '#6e9fa5', '#fd7e14', '#e83e8c', '#17a2b8', '#6f42c1' ]
class Dashboard(TemplateView):
template_name = 'dashboard.html'
def get_context_data(self, **kwargs):
# получение данные из метода по умолчанию
context = super().get_context_data(**kwargs)
# поля, которые мы будем использовать
# df_fields = ['city', 'customer_type', 'gender', 'unit_price', 'quantity',
# 'product_line', 'tax', 'total' , 'date', 'time', 'payment',
# 'cogs', 'profit', 'rating']
# поля для исключения
# df_exclude = ['id', 'cogs']
# создание фрейма данных с записями. chart.js не очень хорошо справляется
# с датами во всех ситуациях, поэтому наш метод преобразует их в строки
# и нужно будет определить столбцы дат и нужный формат.
df = objects_to_df(Purchase, date_cols=['%Y-%m', 'date'])
# создание контекста charts для хранения всех графиков
context['charts'] = []
### каждая диаграмма добавляется одинаково поэтому опишем первую
# создадим объект диаграммы с уникальным chart_id и цветовой палитрой
# если не указан chart_id или цветовая палитра, они будут генерироваться рандомно
# тип диаграмм должен быть идентифицирован здесь и отличаться от типа chartjs
city_payment_radar = Chart('radar', chart_id='city_payment_radar', palette=PALETTE)
# создадим сводную таблицу pandas на основе полей и агрегации, которые мы хотим
# стеки используются либо для группировки, либо для укладки определенного столбца
city_payment_radar.from_df(df, values='total', stacks=['payment'], labels=['city'])
# добавим контекст
context['charts'].append(city_payment_radar.get_presentation())
exp_polar = Chart('polarArea', chart_id='polar01', palette=PALETTE)
exp_polar.from_df(df, values='total', labels=['payment'])
context['charts'].append(exp_polar.get_presentation())
exp_doughnut = Chart('doughnut', chart_id='doughnut01', palette=PALETTE)
exp_doughnut.from_df(df, values='total', labels=['city'])
context['charts'].append(exp_doughnut.get_presentation())
exp_bar = Chart('bar', chart_id='bar01', palette=PALETTE)
exp_bar.from_df(df, values='total', labels=['city'])
context['charts'].append(exp_bar.get_presentation())
city_payment = Chart('groupedBar', chart_id='city_payment', palette=PALETTE)
city_payment.from_df(df, values='total', stacks=['payment'], labels=['date'])
context['charts'].append(city_payment.get_presentation())
city_payment_h = Chart('horizontalBar', chart_id='city_payment_h', palette=PALETTE)
city_payment_h.from_df(df, values='total', stacks=['payment'], labels=['city'])
context['charts'].append(city_payment_h.get_presentation())
city_gender_h = Chart('stackedHorizontalBar', chart_id='city_gender_h', palette=PALETTE)
city_gender_h.from_df(df, values='total', stacks=['gender'], labels=['city'])
context['charts'].append(city_gender_h.get_presentation())
city_gender = Chart('stackedBar', chart_id='city_gender', palette=PALETTE)
city_gender.from_df(df, values='total', stacks=['gender'], labels=['city'])
context['charts'].append(city_gender.get_presentation())
return context
Будем использовать TemplateView. Это очень простое view, к которому можно что-то добавить. Единственный метод, который необходимо расширить – get_context_data, использующийся в Django для получения данных.
Мы вытаскиваем нужные объекты и создаем фрейм данных. Известно, что Chart.js не очень хорошо работает с датами – конвертируем их в строки после создания фрейма. Затем добавляем каждый график в контекст Chart. Это позволяет перебирать графики в коде HTML, т. к. каждая диаграмма представляет собой словарь, содержащий гипертекст и js-запись.
Charts.py
И наконец файл data/charts.py. Весь код легко переносится в другой проект, и вы можете просто поместить его в свой view. Пробежимся по некоторым функциям, а затем перейдем к классу Chart.
def objects_to_df(model, fields=None, exclude=None, date_cols=None, **kwargs):
"""
Возвращает фрейм данных pandas, содержащий записи в модели
``fields`` это необязательный список имен полей. Если это предусмотрено, вернется только
имя.
``exclude`` это необязательный список имен полей. Если это предусмотрено,
именованные элементы исключатся из возвращаемого dict
``date_cols`` chart.js в настоящее время он не очень хорошо обрабатывает даты, поэтому эти
столбцы должны быть преобразованы в строку.
``kwargs`` можно включить, чтобы ограничить модельный запрос конкретными записями
"""
if not fields:
fields = [field.name for field in model._meta.get_fields()]
if exclude:
fields = [field for field in fields if field not in exclude]
records = model.objects.filter(**kwargs).values_list(*fields)
df = pd.DataFrame(list(records), columns=fields)
if date_cols:
strftime = date_cols.pop(0)
for date_col in date_cols:
df[date_col] = df[date_col].apply(lambda x: x.strftime(strftime))
return df
Эта функция принимает модель Django и возвращает все записи. Можно включить некоторые фильтры после всех аргументов имени. Например:
objects_to_df(Purchase, fields=None, exclude=['id'], date_cols=['%Y-%m', 'date'], city='Mandalay')
Результат будет ограничен городом Мандалай. Поля include и exclude работают аналогично Django. Если вы ничего не включаете, будут выведены все поля из модели. Важно отметить, что exclude обрабатываются после include. Таким образом, если включить и исключить столбец, он не будет отображаться.
def get_options():
"""
Дефолтное значение для всех графиков
"""
return {}
Этот код устанавливает параметры по умолчанию для всех графиков.
def generate_chart_id():
"""
Вернет 8 рандомных сгенерированных символов ascii
"""
return ''.join(random.choice(string.ascii_letters) for i in range(8))
Если вы не хотите утруждать себя маркировкой каждого из них, данная функция устанавливает случайный ID, используемый JS для изменения элемента HTML. Этот идентификатор также используется для именования функции JS и переменной, чтобы облегчить отладку.
from palettable.lightbartlein.diverging import BlueDarkRed12_6 as palette # example import
def get_colors():
"""
Цвета из palette.colors или случайным образом сгенерированный список цветов.
Отлично работает с модулем palettable
но не является обязательным и будет вызывать get_random_colors
если palette.colors не задана
"""
try:
return palette.hex_colors
except:
return get_random_colors(6)
get_colors применяется для установки начальных цветов. Если ее передать в палитру, она будет использоваться в качестве основной. Если ничего не передавать и не импортировать из palletable, то цвета будут генерироваться случайным образом.
def get_random_colors(num, colors=[]):
while len(colors) < num:
color = "#{:06x}".format(random.randint(0, 0xFFFFFF))
if color not in colors:
colors.append(color)
return colors
get_random_colors используется, если цвета не были переданы или когда их больше, чем значений. Все начинается с переданных цветов, а в процессе они добавляются случайным образом.
По функциям все. Ниже приведен класс Chart.
@dataclass
class Chart:
"""
Класс в помощь к chart.js.
``datasets`` собственно данные. Содержит данные и варианты их получения.
``labels`` метки для данных.
``chart_id`` уникальный ID диаграммы. Будет сгенерирован рандомно
если таковой не предусмотрен. Это должна быть допустимая JS-переменная.
Не используйте '-'
``palette`` список цветов. Будет сгенерирован, если ни один не указан.
"""
chart_type: str
datasets: List = field(default_factory=list)
labels: List = field(default_factory=list)
chart_id: str = field(default_factory=generate_chart_id)
palette: List = field(default_factory=get_colors)
options: dict = field(default_factory=get_options)
Здесь использовался dataclass – это относительно недавнее дополнение Python и для быстрых классов оно будет хорошим выбором. Установка начальных значений помогает гарантировать, что вы не используете изменяемый объект для экземпляра.
def from_lists(self, values, labels, stacks):
"""
Функция построения графиков из списка
``values`` список датасетов, содержащий одно значение.
``labels`` метки значений.
``stacks`` метки для каждого набора данных в списке значений.
"""
self.datasets = []
# убеждаемся, что у нас правильное количество цветов
if len(self.palette) < len(values):
get_random_colors(num=len(values), colors=self.palette)
# создаем датасет
for i in range(len(stacks)):
self.datasets.append(
{
'label': stacks[i],
'backgroundColor': self.palette[i],
'data': values[i],
}
)
if len(values) == 1:
self.datasets[0]['backgroundColor'] = self.palette
self.labels = labels
from_dataframe помогает использовать несколько строк кода для манипулирования практически любым фреймом данных, чтобы передавать все непосредственно в from_lists для Chart.js. Мы применяем метод pivot_table и Pandas, чтобы создать pivot_table на основе входных данных. Она может выгружать списки, которые нужны для диаграммы.
def from_df(self, df, values, labels, stacks=None, aggfunc=np.sum, round_values=0, fill_value=0):
"""
Функция построения графиков из датафрейма.
``df`` используемый датафрейм.
``values`` имя колонки со значениями.
``stacks`` имя колонки со stack-ами.
``labels`` имя колонки с метками.
``aggfunc`` функция, агрегирующая значения.
``round_values`` десятичный знак для округления.
``fill_value`` используется если значение пустое.
"""
pivot = pd.pivot_table(
df,
values=values,
index=stacks,
columns=labels,
aggfunc=aggfunc,
fill_value=0
)
pivot = pivot.round(round_values)
values = pivot.values.tolist()
labels = pivot.columns.tolist()
stacks = pivot.index.tolist()
self.from_lists(values, labels, stacks)
Заключение
Чтобы внедрить код в своем проекте, скопируйте chart.py и используйте его, как views.py в нашем. Обязательно убедитесь, что базовый файл HTML импортирует chart.js cdn. Bootstrap – по желанию. Удачи в обучении и экспериментах!
Весь код из статьи:
@dataclass
class Chart:
chart_type: str
datasets: List = field(default_factory=list)
labels: List = field(default_factory=list)
chart_id: str = field(default_factory=generate_chart_id)
palette: List = field(default_factory=get_colors)
options: dict = field(default_factory=get_options)
def from_lists(self, values, labels, stacks):
self.datasets = []
if len(self.palette) < len(values):
get_random_colors(num=len(values), colors=self.palette)
for i in range(len(stacks)):
self.datasets.append(
{
'label': stacks[i],
'backgroundColor': self.palette[i],
'data': values[i],
}
)
if len(values) == 1:
self.datasets[0]['backgroundColor'] = self.palette
self.labels = labels
def from_df(self, df, values, labels, stacks=None, aggfunc=np.sum, round_values=0, fill_value=0):
pivot = pd.pivot_table(
df,
values=values,
index=stacks,
columns=labels,
aggfunc=aggfunc,
fill_value=0
)
pivot = pivot.round(round_values)
values = pivot.values.tolist()
labels = pivot.columns.tolist()
stacks = pivot.index.tolist()
self.from_lists(values, labels, stacks)
def get_elements(self):
elements = {
'data': {
'labels': self.labels,
'datasets': self.datasets
},
'options': self.options
}
if self.chart_type == 'stackedBar':
elements['type'] = 'bar'
self.options['scales'] = {
'xAxes': [
{'stacked': 'true'}
],
'yAxes': [
{'stacked': 'true'}
]
}
if self.chart_type == 'bar':
elements['type'] = 'bar'
self.options['scales'] = {
'xAxes': [
{
'ticks': {
'beginAtZero': 'true'
}
}
],
'yAxes': [
{
'ticks': {
'beginAtZero': 'true'
}
}
]
}
if self.chart_type == 'groupedBar':
elements['type'] = 'bar'
self.options['scales'] = {
'xAxes': [
{
'ticks': {
'beginAtZero': 'true'
}
}
],
'yAxes': [
{
'ticks': {
'beginAtZero': 'true'
}
}
]
}
if self.chart_type == 'horizontalBar':
elements['type'] = 'horizontalBar'
self.options['scales'] = {
'xAxes': [
{
'ticks': {
'beginAtZero': 'true'
}
}
],
'yAxes': [
{
'ticks': {
'beginAtZero': 'true'
}
}
]
}
if self.chart_type == 'stackedHorizontalBar':
elements['type'] = 'horizontalBar'
self.options['scales'] = {
'xAxes': [
{'stacked': 'true'}
],
'yAxes': [
{'stacked': 'true'}
]
}
if self.chart_type == 'doughnut':
elements['type'] = 'doughnut'
if self.chart_type == 'polarArea':
elements['type'] = 'polarArea'
if self.chart_type == 'radar':
elements['type'] = 'radar'
return elements
def get_html(self):
code = f'<canvas id="{self.chart_id}"></canvas>'
return code
def get_js(self):
code = f"""
var chartElement = document.getElementById('{self.chart_id}').getContext('2d');
var {self.chart_id}Chart = new Chart(chartElement, {self.get_elements()})
"""
return code
def get_presentation(self):
code = {
'html':self.get_html(),
'js': self.get_js(),
}
return code
Дополнительные материалы:
Комментарии