**Задание по мотивам реального тестового в Делимобиль.**
**Данные:** CSV с GPS-данными автомобилей каршеринга:
[см. код в задании]
GPS-точки приходят каждые 5 минут. Автомобиль считается «на месте», если сместился менее чем на 50 метров.
**Задание:**
1. Реализуйте функцию haversine(lat1, lon1, lat2, lon2) → расстояние в метрах
2. Для каждого автомобиля найдите периоды простоя (серии точек < 50м друг от друга)
3. Найдите автомобиль с самым длинным непрерывным простоем за один день
4. Статистика: распределение длительности простоев, среднее по парку
Структура для ориентира — реальные значения из эталонного решения.
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
# --- 1. Haversine ---
def haversine(lat1, lon1, lat2, lon2):
"""Расстояние между двумя GPS-точками в метрах."""
R = 6_371_000 # радиус Земли в метрах
lat1, lon1, lat2, lon2 = map(np.radians, [lat1, lon1, lat2, lon2])
dlat = lat2 - lat1
dlon = lon2 - lon1
a = np.sin(dlat / 2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2)**2
return 2 * R * np.arcsin(np.sqrt(a))
# --- Генерация GPS-данных ---
np.random.seed(42)
cars = [f'A{i:03d}' for i in range(1, 21)] # 20 авто
base_lat, base_lon = 55.7558, 37.6173 # Москва
rows = []
for car in cars:
lat, lon = base_lat + np.random.uniform(-0.05, 0.05), base_lon + np.random.uniform(-0.1, 0.1)
times = pd.date_range('2024-03-15 00:00', '2024-03-15 23:55', freq='5min')
for t in times:
# Случайные периоды движения и простоя
hour = t.hour
if 2 <= hour <= 6 or np.random.random() < 0.4:
# Простой: минимальное смещение (GPS-шум)
lat += np.random.normal(0, 0.00002)
lon += np.random.normal(0, 0.00003)
else:
# Движение
lat += np.random.normal(0, 0.001)
lon += np.random.normal(0, 0.0015)
rows.append({'car_id': car, 'lat': round(lat, 6), 'lon': round(lon, 6), 'datetime': t})
df = pd.DataFrame(rows)
df['datetime'] = pd.to_datetime(df['datetime'])
print(f"Записей: {len(df):,}, автомобилей: {df['car_id'].nunique()}")
# --- 2. Расстояние между последовательными точками ---
df = df.sort_values(['car_id', 'datetime']).reset_index(drop=True)
df['prev_lat'] = df.groupby('car_id')['lat'].shift(1)
df['prev_lon'] = df.groupby('car_id')['lon'].shift(1)
df['distance_m'] = haversine(df['prev_lat'], df['prev_lon'], df['lat'], df['lon'])
df['distance_m'] = df['distance_m'].fillna(0)
# Точка "на месте" если сместилась < 50 м
df['is_stationary'] = df['distance_m'] < 50
# --- 3. Группировка периодов простоя ---
# Новая группа начинается когда авто начинает/прекращает стоять
df['state_change'] = df.groupby('car_id')['is_stationary'].transform(
lambda x: (x != x.shift()).cumsum()
)
# Только простои
stationary_periods = (df[df['is_stationary']]
.groupby(['car_id', 'state_change'])
.agg(
start=('datetime', 'min'),
end=('datetime', 'max'),
points=('datetime', 'count'),
)
.reset_index())
stationary_periods['duration_min'] = (
(stationary_periods['end'] - stationary_periods['start']).dt.total_seconds() / 60
).round(0)
# Фильтруем: минимум 2 точки (10 минут)
stationary_periods = stationary_periods[stationary_periods['points'] >= 2]
# --- 4. Самый длинный простой ---
longest = stationary_periods.nlargest(1, 'duration_min').iloc[0]
print(f"\n=== Самый длинный простой ===")
print(f"Автомобиль: {longest['car_id']}")
print(f"Начало: {longest['start']}")
print(f"Конец: {longest['end']}")
print(f"Длительность: {longest['duration_min']:.0f} мин ({longest['duration_min']/60:.1f} ч)")
# --- 5. Статистика по парку ---
stats = stationary_periods.groupby('car_id')['duration_min'].agg(['count', 'mean', 'max', 'sum'])
stats.columns = ['periods', 'avg_min', 'max_min', 'total_min']
stats['utilization_pct'] = ((1440 - stats['total_min']) / 1440 * 100).round(1)
print(f"\n=== Статистика простоев (топ-10 по макс. простою) ===")
print(stats.nlargest(10, 'max_min').round(1).to_string())
print(f"\nСреднее по парку:")
print(f" Кол-во простоев/день: {stats['periods'].mean():.1f}")
print(f" Средний простой: {stats['avg_min'].mean():.0f} мин")
print(f" Утилизация: {stats['utilization_pct'].mean():.1f}%")
# --- Визуализация ---
fig, ax = plt.subplots(figsize=(10, 5))
ax.hist(stationary_periods['duration_min'], bins=30, color='#FF5A1F',
edgecolor='white', alpha=0.8)
ax.axvline(stationary_periods['duration_min'].median(), color='black',
linestyle='--', label=f'Медиана: {stationary_periods["duration_min"].median():.0f} мин')
ax.set_xlabel('Длительность простоя, мин')
ax.set_ylabel('Количество периодов')
ax.set_title('Распределение длительности простоев')
ax.legend()
plt.tight_layout()
plt.savefig('idle_distribution.png', dpi=150)
python pandas GPS haversine datetime Делимобиль
Это задание для уровня Middle. Для middle-аналитиков с опытом 1-3 года, требует уверенного владения темой и понимания edge cases.
Подобные задания в категории «Python» регулярно дают на собеседованиях аналитика данных в Яндекс, Сбер, Ozon, Авито, Тинькофф, Wildberries, T-Bank, X5, ВТБ и других крупных IT-компаниях. Тематика: python, pandas, GPS, haversine, datetime.
На реальном собеседовании на подобную задачу отводится 15-30 минут — оцениваются подход, корректность, обработка edge cases. Для тренировки рекомендуем сначала решить самостоятельно, потом сверить с эталонным решением и подсказками.
На zasqlpython.ru есть 482 Python задачи с проверкой через Pyodide, конспекты Python и pandas, AI мок-собеседование с разбором ваших ответов.
← Все задания