Към съдържанието
wannacode

Защо оборотът падна?

Накратко: Проект за анализ на данни от въпроса до доклада: почистване, графики, проверка за шум, разбиване на спада на оборота по брой поръчки, категория, ден и канал и модел за загубата с интервал.

Проблем

„Защо септември е толкова слаб?“

Управителят е видял отчета: оборотът за септември 2026 е 7 128,80 €, а за август 8 812,70 €. Това е 19,1% надолу. Той има три идеи на масата: да спре рекламата, защото „не работи“, да смени доставчика на млечните или да пусне отстъпки.

Всяка идея е отговор на различна причина. Ако спадът е шум, не правиш нищо. Ако е от продукт, гледаш доставчика. Ако хората купуват по-малко, мислиш за цените. Затова въпросът на анализа не е „колко е спадът“, а „къде е спадът, за да знаем какво да поправим“.

В този проект минаваш целия път от курса: данните, почистването, графиката, проверката за шум, разбиването, модела и доклада. Числата са от годината поръчки на магазина, а отговорът не е там, където очакват идеите на управителя.

Идеята

Данните и почистването

Две таблици: year_orders.csv (поръчка, клиент, време, канал, състояние) и year_items.csv (редовете на поръчките). Стойността на поръчката е сборът от редовете ѝ. Отказаните поръчки не са оборот:

Pythonданни: таблиците на магазина

Поръчките са 11 116, а без 657-те отказани остават 10 459. Преди да смяташ, провери почистването: всяка поръчка има стойност (0 липсващи), най-малката е 0,80 €, а дните са 365, от 1.11.2025 до 31.10.2026. Оборотът за годината е 111 186,20 €, същото число като в модула „Подреждане на данни“.

new, paid и shipped са поръчки от последната седмица, които още пътуват. Броиш ги: платени са или ще бъдат. Колоната channel казва откъде е дошла поръчката: сайтът (web), приложението (app) или телефонът (phone).

Напиши почистването сам. orders['ordered_at'] да е дата, items['revenue'] оборотът на всеки ред, orders['value'] стойността на всяка поръчка, а sold поръчките без отказаните.

Pythonданни: таблиците на магазина

Идеята

Първо графиката

Преди да търсиш причина, виж спада в контекста на годината:

Pythonданни: таблиците на магазина

Септември е 7 128,80 € срещу 8 812,70 € през август: −19,1%. Октомври е 9 920,40 €, значи спадът не продължава. Графиката показва и друго: декември е над 15 000 €, а януари и февруари също са ниски. Септември е спад насред нормални месеци, а не част от сезон.

Септември има 30 дни, а август 31. Дали това не обяснява част от спада? Запиши в per_day оборота на ден за всеки месец, а в change промяната на оборота на ден от август към септември в проценти.

Pythonданни: таблиците на магазина

Идеята

Шум ли е?

Месеците винаги се различават малко. Преди да търсиш причина, провери дали спадът е по-голям от обичайния шум. Сравняваш дните: 31 дни на август срещу 30 на септември, с пермутационния тест от урока за хипотезите. Отдолу е промяната на оборота на ден от месец на месец:

Pythonданни: таблиците на магазина

Септемврийският ден носи средно с 46,65 € по-малко. От 10 000 разбърквания на дните само 74 дават поне толкова голяма разлика: p = 0,0074. Нормалните месеци от март до август се движат между −6,5% и +5,8%, а септември е −16,4%. Февруари (+16,8%) е връщането след празниците, а октомври (+34,7%) е връщането след септември.

Спадът не е шум. Има смисъл да търсиш причина.

1. Пермутационният тест за дните на август срещу дните на септември дава p = 0,0074. Какво следва от това?

Идеята

Разбий спада

Оборотът е брой поръчки по стойност на поръчка. Първото разбиване е кое от двете пада:

Pythonданни: таблиците на магазина

Поръчките падат от 854 на 701 (−17,9%), а средната поръчка е почти същата: 10,32 € и 10,17 €. Кошницата не е по-малка, поръчките са по-малко. Отстъпките не са отговорът.

Може ли да е продукт, например млечните на новия доставчик? Оборотът по категории:

Pythonданни: таблиците на магазина

Пада всяка категория, от −9,0% (плодове и зеленчуци) до −34,3% (напитки). Подаръчната карта е +14,3%, но това са 160 € срещу 140 € през август, няколко карти. Ако виновникът беше един продукт, другите категории щяха да са нормални. Спадът е навсякъде: не е продуктът.

Дали спадът не е в един ден от седмицата, например магазинът не е работил в четвъртък? Запиши в by_wd средния оборот на ден за всеки ден от седмицата (редове 0–6) за август и септември (колони), и колона change_% с промяната в проценти.

Pythonданни: таблиците на магазина

Идеята

Каналът

Остана една колона, която още не си гледал: channel. Оборотът по месец и канал:

Pythonданни: таблиците на магазина

Ето го. Сайтът (web) е 4 517,00 € през август и 4 660,80 € през септември. Телефонът (phone) е 999,70 € и 1 028,70 €. Те са нормални. Приложението (app) пада от 3 296,00 € на 1 439,30 € и се връща на 3 755,40 € през октомври.

Спадът на приложението е 1 856,70 €, повече от целия спад на магазина (1 683,90 €): другите два канала дори леко растат. Затова всяка категория и всеки ден падат заедно: приложението продава всичко, всеки ден.

Колко от оборота идва от приложението в обикновен месец? Запиши в share дела на app в оборота на всеки месец, в проценти.

Pythonданни: таблиците на магазина

Идеята

Кога точно?

„Септември“ е грубо. Екипът на приложението ще пита за дата. Поръчките от приложението ден по ден, с плъзгащото средно за 7 дни, и дните около двата края:

Pythonданни: таблиците на магазина

Линията пада в началото на септември и се връща в началото на октомври. Таблицата показва краищата. На 2.09 приложението има 9 поръчки, на 3.09 само 3, а на 5.09 нула, докато сайтът има 25 и 15. В края: 6 поръчки на 1.10, 8 на 2.10 и 12 на 3.10.

Плъзгащото средно размазва краищата с по няколко дни, затова датите се търсят в дните. Началото е ясно: 3.09. Краят е по-мътен: 2.10 вече е наполовина нормален, а от 3.10 приложението е като преди. Спадът е от 3.09 до 2.10, с точност до ден.

Намери датите с правило, а не на око. start е първият ден от 1.08.2026 нататък, в който приложението има под 20% от поръчките за деня. back е първият ден след start, в който поръчките от приложението са поне колкото медианата на нормалните дни (до 31.08).

Pythonданни: таблиците на магазина

Идеята

Колко струва спадът?

Управителят ще пита колко пари е струвало. Наивният отговор е „август минус септември“: 1 683,90 €. Но той приема, че септември трябваше да е като август. Октомври е по-силен от август, затова може и септември да е трябвало да е по-силен.

По-добро сравнение дават каналите, които работят. В нормално време приложението носи определен дял спрямо сайта и телефона. Този дял k е модел с едно число: предсказва оборота на приложението от оборота на другите два канала за същите дни. Учиш го от дните преди спада и го проверяваш върху дни, които не е виждал: октомври след спада.

Pythonданни: таблиците на магазина

На всеки 1 € от сайта и телефона приложението носи 0,641 €. За октомври след спада моделът казва 3 688,92 €, а истината е 3 614,60 €: грешка 2,1%. За дните от 3.09 до 2.10 моделът очаква 3 713,41 €, а приложението е донесло 1 292,60 €. Загубата е 2 420,81 €, с 737 € повече от наивното сравнение.

Колко е точно това? Делът k за 30 дни не е винаги един и същ. С bootstrap теглиш по 30 нормални дни, 10 000 пъти, и гледаш колко се мести загубата:

Pythonданни: таблиците на магазина

В 30 случайни нормални дни k е между 0,457 и 0,906. 95% интервал за загубата е от 1 802,46 € до 3 142,14 €. В доклада това е „около 2 400 €, между 1 800 и 3 150 €“. Оценката е груба, но и долната граница е над наивните 1 683,90 €.

По-точно, за любопитните

Моделът app = k × (web + phone) има един признак и едно число, като линейна регресия без свободен член. Проверката му е като в модула за модели: учиш го върху едни дни (преди 3.09) и го мериш върху други (след 2.10). Той приема, че каналите вървят заедно, и проверката върху октомври го потвърждава. Ако през септември клиенти на приложението бяха минали на сайта, моделът щеше да надцени загубата: щеше да брои като изгубени поръчки, които всъщност са дошли от сайта. Сайтът през септември е +3,2% спрямо август, в рамките на обичайното движение, затова повечето изгубени поръчки не са отишли другаде.

Колко поръчки е изгубило приложението? Направи същия модел за броя поръчки: k е app / (web + phone) в дните преди 3.09, expected е колко поръчки от приложението щеше да има от 3.09 до 2.10, actual колко е имало, а lost_pct каква част от очакваните са изгубени, в проценти.

Pythonданни: таблиците на магазина

Идеята

Докладът

Анализът свършва с разказ, който някой може да прочете за минута. Три изречения, една графика, границите.

Pythonданни: таблиците на магазина
  1. Какво: оборотът за септември е 19,1% под август, защото от 3.09 до 2.10 приложението е имало около две трети по-малко поръчки; сайтът и телефонът са нормални, а от 3.10 и приложението е нормално.
  2. Колко: загубата е около 2 400 € (между 1 800 и 3 150 €), около 240 поръчки.
  3. Какво да направим: да проверим какво се е променило в приложението около 3.09 (обновяване, кошница, плащане) и да сложим сигнал, когато делът на един канал падне.

Границите. Данните казват кога и къде: в приложението, от 3.09. Те не казват защо. Обновяване на приложението около 3.09 е хипотеза, а не факт: проверяват я екипът на приложението, логовете на кошницата и грешките на плащането за тази дата. Може да е и нещо друго: магазинът на приложенията, срок на сертификат, промяна в платежния доставчик. Графиката не различава тези причини. Затова докладът казва „проверете“, а не „това беше“.

Рекламата, доставчикът и цените не са причината. Нито една от трите идеи на управителя не би върнала оборота.

1. Кое изречение е за доклада?

Упражнение

Сигнал за следващия път

Спадът е продължил месец, защото никой не е гледал канала. Правилото за сигнал трябва да хване спада бързо и да мълчи в нормално време. Сигнал, който вика всяка седмица, никой не гледа.

Направи сигнала. week е броят поръчки по канал за последните 7 дни (rolling(7).sum()), app_share делът на app в тях, а alerts дните, в които app_share е под 0.25. Колко тревоги има преди 3.09 и кога е първата?

Pythonданни: таблиците на магазина

Идеята

Какво следва

Курсът свършва тук, а работата с данни продължава. Накъде:

  • Бележници. Направи този проект в JupyterLab, на своя компютър: клетка по клетка, с изхода под всяка. Бележникът е и докладът: код, графики и текст на едно място.
  • По-големи данни. pandas държи всичко в паметта и работи на едно ядро. Polars има същите идеи (групи, съединяване, дати), но ползва всички ядра и чете файлове, по-големи от паметта: pl.read_csv('year_orders.csv').group_by('channel').len() брои поръчките по канал.
  • Табла. Сигналът от упражнението е добре да е на табло, което се обновява само. Streamlit и Dash правят уеб табло от Python скрипт, с графики и филтри.
  • Графики. seaborn и plotly са слоеве над идеите от matplotlib: seaborn дава статистически графики с един ред, а plotly интерактивни, с увеличаване и подсказки.
  • Бази данни. В истински магазин поръчките са в база, а не в CSV. pd.read_sql чете резултата от SQL заявка направо в DataFrame. SQL се учи в курса Бази данни и SQL.

Пътят остава същият: въпросът, данните, почистването, разглеждането, изводът с границите му и разказът.

Тест

Провери се

1. Защо първото нещо в проекта е въпросът „какво ще решим с отговора“?
2. Сборът на оборота в сряда е +18,0% от август към септември, а средният оборот в сряда е −5,6%. На кое вярваш?
3. Защо моделът предсказва приложението от web и phone, а не просто приема, че септември трябваше да е като август?
4. Загубата е 2 420,81 €, с 95% интервал от 1 802,46 € до 3 142,14 €. Как я казваш в доклада?
5. Сигналът е „делът на app в поръчките за 7 дни е под 0,25“. Защо 7 дни, а не всеки ден поотделно?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·