Практикум — Pandas Data Viewer
О практикуме
Pandas Data Viewer — учебное десктопное приложение: открыть таблицу из .csv или Excel, просмотреть строки, найти значение по всем столбцам и вывести описательную статистику. Всё в одном окне на Tkinter (входит в стандартную поставку Python) и pandas (библиотека для табличного анализа).
Зачем такой проект, если есть Excel и Jupyter? Потому что он связывает два навыка, которые в реальной работе идут рядом:
- аналитика — загрузка, фильтрация,
describe(), типы столбцов; - интерфейс — кнопки, диалоги, таблица на экране, обратная связь пользователю.
Аналог в духе «мини-Excel на Python» полезен для отчётов, проверки выгрузок из CRM или логов без запуска тяжёлого BI-стека. Теория таблиц — в Анализ данных — pandas, NumPy, SciPy; виджеты и цикл событий — в Tkinter и GUI и Первая программа на Tkinter. Скрипты без GUI — Pandas — типовые операции. Образец для сверки — F:\Projects\Python\TestPandas.
Нужны Python 3.10+, базовые классы и списки, установленные pandas и openpyxl. Желательно пройти первую программу на Tkinter и хотя бы раз открыть CSV в Lab.
Маршрут раздела «Анализ данных» — о разделе, пункт 2 (Data Science).
Оценка времени — 2–3 часа при прохождении всех этапов подряд с самопроверкой после каждого шага.
Чему учит практикум
| Навык | Что именно тренируем | Где в коде |
|---|---|---|
| Загрузка данных | read_csv, read_excel, обработка ошибок | open_file |
| EDA | dtypes, describe(), размер таблицы | show_stats |
| Фильтрация | булева маска, str.contains | apply_filter |
| GUI | Treeview, filedialog, messagebox, StringVar | _build_ui, populate_table |
| Архитектура | состояние (self.df) отдельно от отрисовки | класс DataViewerApp |
Ключевые определения
- DataFrame — двумерная таблица pandas: строки — записи, столбцы — атрибуты с именами и типами. Подробнее — обзор Pandas.
- EDA (Exploratory Data Analysis, разведочный анализ) — первичный осмотр данных: сколько строк, какие типы, есть ли выбросы. В Excel это лист + сводные; здесь —
info,dtypes,describe(). Контекст — Data Science, 428 — типовые операции. - Булева маска — столбец или Series из
True/False;df[mask]оставляет только строки, где условие истинно. Тот же приём — в фильтрации Pandas. - Treeview — виджет Tkinter для табличного и древовидного вывода; мы используем режим «только заголовки» (
show="headings"), без колонки-дерева. - Главный цикл (
mainloop) — бесконечное ожидание кликов и ввода; без него окно сразу закроется — см. 3111.
Карта этапов
| Этап | Фокус | Результат |
|---|---|---|
| 0 | Окружение | venv, зависимости, пустое окно |
| 1 | Каркас UI | Панель кнопок, строка поиска, статус |
| 2 | Загрузка файла | read_csv / read_excel, обработка ошибок |
| 3 | Таблица | Treeview с колонками DataFrame |
| 4 | Поиск | Маска по всем столбцам, сброс |
| 5 | Статистика | dtypes, describe() в диалоге |
| 6 | Ревизия | Итоговая самопроверка |
Правило прохождения — после каждого этапа запускайте python app.py. Не переходите дальше, пока не отмечены пункты самопроверки — тот же подход, что в отладке и разработке.
Этап 0 — окружение
Цель — отдельная папка проекта, виртуальное окружение, зафиксированные зависимости и пустое окно с заголовком.
Зачем venv и requirements.txt
Виртуальное окружение изолирует пакеты проекта от системного Python: на одном компьютере могут жить разные версии pandas для разных задач. Файл requirements.txt фиксирует состав окружения — коллега или вы через месяц воспроизведёте те же версии одной командой pip install -r requirements.txt. Подробнее — Зависимости Python.
mkdir pandas-viewer && cd pandas-viewer
python -m venv .venv
.venv\Scripts\activate
pip install "pandas>=2.0.0" "openpyxl>=3.1.0"
requirements.txt:
pandas>=2.0.0
openpyxl>=3.1.0
Разбор команд.
python -m venv .venvсоздаёт каталог.venvс копией интерпретатора иpip..venv\Scripts\activate(Windows) подключает это окружение в текущей сессии терминала; в Linux/macOS —source .venv/bin/activate.openpyxl— движок для чтения.xlsx; без негоpd.read_excel()завершится ошибкой импорта, хотя CSV будет работать.
Тестовые данные
Создайте sample_data.csv — небольшая таблица сотрудников для проверки поиска и статистики:
name,age,city,salary
Анна,28,Москва,85000
Борис,34,Санкт-Петербург,92000
Виктория,25,Казань,71000
Григорий,41,Новосибирск,105000
Дарья,31,Москва,88000
Егор,29,Екатеринбург,79000
Жанна,36,Самара,83000
Иван,22,Москва,65000
В файле смешаны типы:
name,city— строки (objectв pandas);age,salary— целые числа (int64);- в
cityтри вхождения «Москва» — удобно проверить фильтр на этапе 4.
Чтение такого CSV без pandas — модуль csv в stdlib; здесь сразу переходим к DataFrame.
Минимальное окно
app.py:
import tkinter as tk
def main() -> None:
root = tk.Tk()
root.title("Pandas Data Viewer")
root.geometry("900x600")
root.mainloop()
if __name__ == "__main__":
main()
Разбор.
tk.Tk()— корневое окно приложения; один экземпляр на программу.title(...)— текст в заголовке окна ОС.geometry("900x600")— ширина × высота в пикселях; позже добавимminsize, чтобы окно не сжималось до нечитаемого размера.if __name__ == "__main__"— точка входа при прямом запуске; при импорте модуляmain()не вызовется — см. if name == "main".mainloop()— цикл обработки событий; без него процесс завершится и окно исчезнет.
Самопроверка
-
python app.pyоткрывает пустое окно с заголовком «Pandas Data Viewer». -
pip listпоказываетpandasиopenpyxl. - Повторный запуск не выдаёт ошибок импорта.
Этап 1 — каркас интерфейса
Цель — собрать все зоны экрана до подключения pandas: панель действий, поиск, таблица, строка статуса.
Слои интерфейса
Десктопное приложение удобно мыслить слоями — сверху вниз:
- Toolbar — кнопки «Открыть файл» и «Статистика», метка с путём к файлу.
- Filter bar — поле поиска и «Сбросить».
- Table area —
Treeviewс вертикальной и горизонтальной прокруткой. - Status bar — краткое сообщение о состоянии (загружено N строк, найдено M).
Такое деление повторяет привычный Excel: лента → фильтр → сетка → строка состояния. Теория компоновки — pack и grid в 3111.
Замените app.py:
import tkinter as tk
from tkinter import ttk
class DataViewerApp:
def __init__(self, root: tk.Tk) -> None:
self.root = root
self.root.title("Pandas Data Viewer")
self.root.geometry("900x600")
self.root.minsize(700, 400)
self.df = None
self.file_path = None
self._build_ui()
def _build_ui(self) -> None:
toolbar = ttk.Frame(self.root, padding=8)
toolbar.pack(fill=tk.X)
ttk.Button(toolbar, text="Открыть файл", command=self.open_file).pack(
side=tk.LEFT, padx=(0, 8)
)
ttk.Button(toolbar, text="Статистика", command=self.show_stats).pack(
side=tk.LEFT, padx=(0, 8)
)
self.file_label = ttk.Label(toolbar, text="Файл не выбран", foreground="gray")
self.file_label.pack(side=tk.LEFT, padx=8)
filter_frame = ttk.Frame(self.root, padding=(8, 0, 8, 8))
filter_frame.pack(fill=tk.X)
ttk.Label(filter_frame, text="Поиск:").pack(side=tk.LEFT)
self.search_var = tk.StringVar()
ttk.Entry(filter_frame, textvariable=self.search_var, width=40).pack(
side=tk.LEFT, padx=8
)
ttk.Button(filter_frame, text="Сбросить", command=self.reset_filter).pack(
side=tk.LEFT
)
table_frame = ttk.Frame(self.root, padding=8)
table_frame.pack(fill=tk.BOTH, expand=True)
self.tree = ttk.Treeview(table_frame, show="headings")
vsb = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview)
hsb = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
table_frame.rowconfigure(0, weight=1)
table_frame.columnconfigure(0, weight=1)
self.status = ttk.Label(self.root, text="Готово", padding=8, relief=tk.SUNKEN)
self.status.pack(fill=tk.X, side=tk.BOTTOM)
def open_file(self) -> None:
self.set_status("Кнопка «Открыть файл» — на этапе 2")
def show_stats(self) -> None:
self.set_status("Кнопка «Статистика» — на этапе 5")
def reset_filter(self) -> None:
self.search_var.set("")
def set_status(self, text: str) -> None:
self.status.config(text=text)
def main() -> None:
root = tk.Tk()
DataViewerApp(root)
root.mainloop()
if __name__ == "__main__":
main()
Разбор класса и виджетов
Состояние приложения.
self.df = None— таблица ещё не загружена;Noneотличаем от пустого DataFrame.self.file_path— путь для метки в toolbar; пригодится, если добавите «перезагрузить файл».
ttk против tk.
- Модуль
ttk(themed Tk) даёт виджеты в стиле ОС — кнопки и таблица выглядят аккуратнее классического Tk. Frameгруппирует виджеты;padding=8— внутренние отступы в пикселях.
Компоновка.
- В toolbar и filter bar —
pack(side=tk.LEFT)— элементы в ряд слева направо. - Область таблицы —
pack(fill=tk.BOTH, expand=True)— растягивается на всё свободное место при ресайзе окна. - Внутри
table_frame—grid: таблица в (0,0), вертикальная полоса в (0,1), горизонтальная в (1,0). rowconfigure(0, weight=1)иcolumnconfigure(0, weight=1)— при увеличении окна растёт именно ячейка с таблицей, а не полосы прокрутки.sticky="nsew"— виджет «приклеен» ко всем сторонам ячейки (north-south-east-west).
Treeview и прокрутка.
show="headings"скрывает служебную колонку#0с деревом; остаётся плоская таблица с именованными столбцами.yscrollcommand/xscrollcommandсвязывают полосы прокрутки с таблицей — стандартный паттерн Tkinter.
Заглушки обработчиков.
command=self.open_fileпередаёт ссылку на метод, без скобок — иначе функция вызовется при создании кнопки, а не при клике (см. 311 — пример кнопки).- Заглушки меняют строку статуса — вы сразу видите, что кнопки подключены.
Самопроверка
- Окно показывает две кнопки, поле поиска, пустую таблицу и строку «Готово» внизу.
- Кнопки меняют текст в строке статуса.
- При сужении окна таблица сжимается, но не наезжает на toolbar.
Этап 2 — загрузка CSV и Excel
Цель — диалог выбора файла, чтение в DataFrame, сохранение в self.df, отображение пути и размера таблицы.
Форматы и функции чтения
| Расширение | Функция pandas | Зависимость |
|---|---|---|
.csv | pd.read_csv(path) | только pandas |
.xlsx | pd.read_excel(path) | openpyxl |
.xls | pd.read_excel(path) | может потребоваться xlrd |
CSV (Comma-Separated Values) — текстовый формат: строка = запись, поля разделены запятой или точкой с запятой. Excel хранит листы в бинарном или XML-контейнере; pandas скрывает детали, но движок чтения нужно установить отдельно.
Типовые параметры read_csv (кодировка, разделитель, типы столбцов) — загрузка данных в 428 и примеры в Lab.
Добавьте импорты в начало app.py:
from tkinter import filedialog, messagebox
import pandas as pd
Замените метод open_file:
def open_file(self) -> None:
path = filedialog.askopenfilename(
title="Выберите файл",
filetypes=[
("Таблицы", "*.csv *.xlsx *.xls"),
("CSV", "*.csv"),
("Excel", "*.xlsx *.xls"),
("Все файлы", "*.*"),
],
)
if not path:
return
try:
if path.lower().endswith(".csv"):
df = pd.read_csv(path)
else:
df = pd.read_excel(path)
except Exception as exc:
messagebox.showerror("Ошибка", f"Не удалось прочитать файл:\n{exc}")
return
self.df = df
self.file_path = path
self.search_var.set("")
self.file_label.config(text=path, foreground="black")
self.set_status(f"Загружено: {len(df)} строк, {len(df.columns)} столбцов")
Разбор метода open_file
Диалог выбора файла.
filedialog.askopenfilename()блокирует окно до выбора файла или «Отмена»; возвращает строку пути или пустую строку.filetypes— список пар «описание, шаблон»; пользователь может отфильтровать только CSV или только Excel.
Ветвление по расширению.
path.lower().endswith(".csv")— простая эвристика; для продакшена иногда смотрят MIME-тип или пробуют оба читателя.read_excelбезsheet_nameберёт первый лист книги — для учебного проекта достаточно.
Обработка ошибок.
try/exceptловит битый CSV, отсутствиеopenpyxl, занятый Excel-файл, неверную кодировку.messagebox.showerrorпоказывает текст исключения пользователю; приложение не падает — важный принцип GUI, см. обработку исключений.
Обновление состояния.
self.df = df— единый источник правды для таблицы; поиск и статистика читают только его.self.search_var.set("")— сброс старого запроса при новом файле.foreground="black"у метки пути — визуальный сигнал «файл выбран» (на этапе 0 был серый «Файл не выбран»).
На следующем этапе добавим вызов self.populate_table(df) сразу после успешной загрузки.
Самопроверка
- «Открыть файл» →
sample_data.csv→ в статусе «Загружено: 8 строк, 4 столбца». - Метка слева показывает полный путь к файлу.
- «Отмена» в диалоге не меняет состояние и не вызывает ошибку.
- Повреждённый или неподходящий файл даёт диалог с ошибкой, программа продолжает работать.
Этап 3 — вывод таблицы в Treeview
Цель — отобразить столбцы и строки DataFrame в Treeview, очистить таблицу при новой загрузке, ограничить число строк для отзывчивости GUI.
Почему не выводить DataFrame напрямую
Pandas умеет print(df) и рендер в Jupyter, но в Tkinter нет встроенного «виджета DataFrame». Нужен мост:
- взять имена столбцов → заголовки
Treeview; - пройти по строкам →
insertс списком значений; NaNи нестандартные типы → строки для отображения.
Для очень больших файлов полный перебор в GUI-потоке заморозит окно — поэтому на первом проходе показываем не более 1000 строк и предлагаем поиск для сужения выборки. Пакетная обработка больших CSV — 433 — пакетная работа с данными.
Добавьте вызов в open_file после присвоения self.df:
self.populate_table(df)
self.set_status(f"Загружено: {len(df)} строк, {len(df.columns)} столбцов")
Метод populate_table:
def populate_table(self, df: pd.DataFrame) -> None:
self.tree.delete(*self.tree.get_children())
self.tree["columns"] = list(df.columns)
for col in df.columns:
self.tree.heading(col, text=str(col))
self.tree.column(col, width=120, anchor=tk.W, minwidth=60)
display_df = df.head(1000)
for _, row in display_df.iterrows():
values = ["" if pd.isna(v) else str(v) for v in row]
self.tree.insert("", tk.END, values=values)
if len(df) > 1000:
self.set_status(
f"Показано 1000 из {len(df)} строк. Используйте поиск для фильтрации."
)
Разбор populate_table
Очистка.
get_children()возвращает id всех строк дерева;delete(*...)удаляет их перед новой отрисовкой.- Без очистки при повторной загрузке или фильтре строки накладывались бы друг на друга.
Заголовки и ширина.
self.tree["columns"] = list(df.columns)— динамический набор столбцов: разные файлы — разная схема.heading(col, text=str(col))— подпись в шапке;str(col)на случай нестандартных имён.anchor=tk.W— выравнивание ячеек по левому краю (удобно для текста и чисел).
Строки данных.
df.head(1000)— срез первых 1000 строк; защита интерфейса от зависания.iterrows()возвращает пары(индекс, Series); индекс_не используем.pd.isna(v)— пропуск (NaN,NA) показываем пустой ячейкой, а не текстомnan.str(v)приводит числа и даты к строке для единообразного вывода в Treeview.
Ограничение учебного подхода.
iterrows()медленный на сотнях тысяч строк; в продакшене —itertuples(), порционная подгрузка или отдельный слой кэша. Для 8–1000 строк наш вариант достаточен.
Самопроверка
- После открытия
sample_data.csvвидны столбцыname,age,city,salaryи 8 строк. - Горизонтальная и вертикальная прокрутка работают при сужении окна.
- Повторное открытие того же файла не дублирует строки.
Этап 4 — поиск по всем столбцам
Цель — «живой» фильтр при вводе в поле «Поиск»: оставить строки, где запрос встречается хотя бы в одном столбце.
Идея фильтра
В Excel — автофильтр по столбцу. Здесь — глобальный поиск по всей таблице, как строка поиска в почте или проводнике.
Алгоритм:
- привести все ячейки к строкам (
astype(str)); - для каждого столбца проверить
str.contains(query); - объединить столбцы через
any(axis=1)— строка подходит, если хоть один столбец совпал; df[mask]— отфильтрованный DataFrame;- снова
populate_table(filtered).
Тот же механизм булевой маски, что при df[df["city"] == "Москва"], только условие сложнее — см. фильтрацию в обзоре и примеры в Lab.
В _build_ui замените создание search_var:
self.search_var = tk.StringVar()
self.search_var.trace_add("write", lambda *_: self.apply_filter())
Методы фильтрации:
def apply_filter(self) -> None:
if self.df is None:
return
query = self.search_var.get().strip().lower()
if not query:
self.populate_table(self.df)
self.set_status(f"Загружено: {len(self.df)} строк")
return
mask = self.df.astype(str).apply(
lambda col: col.str.lower().str.contains(query, na=False)
).any(axis=1)
filtered = self.df[mask]
self.populate_table(filtered)
self.set_status(f"Найдено: {len(filtered)} из {len(self.df)} строк")
def reset_filter(self) -> None:
self.search_var.set("")
if self.df is not None:
self.populate_table(self.df)
self.set_status(f"Загружено: {len(self.df)} строк")
Разбор поиска
Привязка к полю ввода.
StringVar— переменная Tkinter, связанная сEntry; изменения текста видны и виджету, и коду.trace_add("write", callback)вызываетapply_filterпри каждом символе — «живой» фильтр без кнопки «Найти».lambda *_:игнорирует служебные аргументы trace; обработчик можно было бы назватьdef _on_search_changed(self, *_):.
Нормализация запроса.
strip().lower()— поиск без учёта регистра и лишних пробелов; «Москва» и «москва» дают один результат.
Построение маски.
astype(str)— число92000станет строкой"92000", поэтому запрос92найдёт зарплату Бориса.col.str.lower().str.contains(query, na=False)— по каждому столбцу Series из True/False;na=False— пропуски не считаем совпадением..any(axis=1)— по строкам: True, если хотя бы один столбец в строке True.filtered = self.df[mask]— классическая индексация DataFrame булевой маской.
Сброс.
reset_filterочищает поле; пустой запрос вapply_filterвосстанавливает полную таблицу.
Самопроверка
- Поиск
москваоставляет три строки (Анна, Дарья, Иван). - Поиск
92находит Бориса (зарплата 92000). - Поиск
бориснаходит одну строку независимо от регистра. - «Сбросить» возвращает все 8 строк.
- Пустое поле поиска показывает полную таблицу.
Этап 5 — описательная статистика
Цель — диалог с размером таблицы, типами столбцов и describe() для числовых полей — минимальный EDA в один клик.
Что такое описательная статистика
Описательная статистика суммирует распределение значений без построения модели:
- count — сколько непустых значений;
- mean — среднее;
- std — разброс вокруг среднего;
- min, 25%, 50%, 75%, max — минимум, квартили, медиана, максимум.
В Excel то же даёт «Описательная статистика» из надстройки «Анализ данных» — см. 42 — статистика. В pandas — один вызов describe().
Перед статистикой всегда смотрят типы (dtypes): столбец с цифрами, прочитанный как строка, даст неверный mean. Чек-лист очистки — 427 — подготовка в Pandas.
Замените заглушку show_stats:
def show_stats(self) -> None:
if self.df is None:
messagebox.showinfo("Статистика", "Сначала откройте файл с данными.")
return
info = [
f"Строк: {len(self.df)}",
f"Столбцов: {len(self.df.columns)}",
"",
"Типы данных:",
self.df.dtypes.to_string(),
"",
"Описательная статистика (числовые столбцы):",
]
numeric = self.df.select_dtypes(include="number")
if numeric.empty:
info.append(" (числовых столбцов нет)")
else:
info.append(numeric.describe().to_string())
messagebox.showinfo("Статистика", "\n".join(info))
Разбор show_stats
Проверка данных.
- Если
self.df is None, пользователь ещё не открыл файл — показываем подсказку, а не пустой отчёт.
Сбор текста отчёта.
- Список
infoс f-строками и пустыми строками""для разделения блоков. dtypes.to_string()— компактная таблица «столбец → тип»; дляsample_data.csvувидитеint64уageиsalary,objectуnameиcity.
Только числовые столбцы.
select_dtypes(include="number")отсекает строки вродеname—describe()для текста бессмысленен.numeric.empty— отдельное сообщение, если в файле нет чисел (например, только категории).
Диалог.
messagebox.showinfo— модальное окно с кнопкой OK; для длинного текста в учебном проекте достаточно; для продакшена — отдельное окно сTextи прокруткой (3112 — справочник Tkinter).
Что проверить на sample_data.csv.
age: mean около 30.75, min 22, max 41;salary: mean около 84125, max 105000 у Григория;nameиcityв блокеdescribe()не появляются.
Самопроверка
- Без загруженного файла — сообщение «Сначала откройте файл с данными».
- Для
sample_data.csvв статистике видныageиsalaryс mean, min, max. - Столбец
nameне попадает в числовой блокdescribe(). - После фильтрации статистика по-прежнему считается по полному
self.df(так задумано; фильтр только для отображения).
Этап 6 — ревизия и дальнейшие шаги
Цель — убедиться, что приложение цельное; понять, куда развивать проект дальше.
Итоговая архитектура
Полный app.py совпадает с образцом в F:\Projects\Python\TestPandas\app.py. Запуск:
python app.py
Итоговая самопроверка
- Открываются
.csvи.xlsx(для Excel нуженopenpyxl). - Таблица, поиск и статистика работают на
sample_data.csv. - Ошибки чтения файла показываются в диалоге, программа не завершается.
- Код в одном файле
app.py— для учебного проекта нормально; при росте логику можно вынести вdata_io.pyиfilters.py.
Что добавить самостоятельно
| Улучшение | Зачем | Подсказка |
|---|---|---|
| Экспорт отфильтрованных данных | сохранить результат поиска | filtered.to_csv(...) в apply_filter |
| Сортировка по клику на заголовок | удобство как в Excel | tree.heading(col, command=...) + df.sort_values |
| График зарплат по городам | визуальный EDA | Matplotlib — графики, groupby + bar |
| Статистика по текущему фильтру | отчёт только по видимым строкам | отдельное поле self.filtered_df |
| Тесты логики фильтра | регрессии без GUI | вынести маску в функцию, pytest |
| Кодировка CSV | файлы в cp1251 | read_csv(path, encoding="cp1251") — 428 |
Связанные материалы
Pandas и аналитика
- Анализ данных — pandas, NumPy, SciPy — теория DataFrame, масок, groupby.
- Pandas — объединение таблиц, своды и временные ряды — merge, pivot, даты.
- Pandas — типовые операции — скрипты без GUI с построчным разбором.
- 428 — типовые операции при анализе — шпаргалка EDA.
- Data Science — стек и роли в экосистеме данных.
Интерфейс и десктоп
- Tkinter и GUI · Первая программа на Tkinter — виджеты,
pack,grid,mainloop. - Справочник по Tkinter — элементы UI — рецепты виджетов.
- Десктопные приложения — о разделе — контекст жанра.
Смежные инструменты
- NumPy — массивы и матрицы — основа под pandas.
- Matplotlib — графики — следующий шаг после таблицы.
- Зависимости Python — venv,
pip,pyproject.toml.
В подборках
Аналитика данных — Анализ данных — о разделе, Python — о разделе, типовые операции Pandas, практикум в пункте 2 Data Science.
Десктоп на Python — Tkinter и GUI, десктопные приложения, Tkinter — примеры в Lab.