Autor Zpráva
danhill
Profil
Zdravím phpborci,
potřeboval bych trochu poradit se zdánlivě stupidním problémem, který ale nějak nemůžu v hlavě přelomit :)
Třeba budete mít nějaký nápad jak to nejjednodušeji udělat.
Mám graf a do něj cpu datum a nějakou cenu toho dne z multiarray :
Array
(
    [0] => Array
        (
            [price] => 293253
            [date] => 2020-10-21
        )
    [1] => Array
        (
            [price] => 296339
            [date] => 2020-10-22
        )
    [2] => Array
        (
            [price] => 296729
            [date] => 2020-10-23
        )
....
Tak, ale z tohoto zdrojového pole potřebuji udělat nový array s průměrnou hodnotou pro každý den a to za x dní. Řekněme třeba za 30 předcházejících dní.
Tedy nikoli průměr ze všech hodnot, ale od 31 dne se z průměrované hodnoty ten první den odečte a tak pořád dál a dál abych měl pro každý nový den průměrnou hodnotu za posledních 30 dní.
Děkuji za tipy. Nějak jsem se do toho zacyklil ...
RastyAmateur
Profil
Takže máš pole, kde den po dni máš nějakou hodnotu price a ty chceš přidat novou hodnotu, nazvěme ji třeba rolling_mean, která bude obsahovat vždy průměr za posledních N dní?

No tak průměr je suma podělená počtem. Spočítáme si tedy, jaká je suma za prvních N dní.
(array_slice vezme jen prvních N prvků pole, array_map si vytáhne jen ty ceny a array_sum je sečte)
$OVER_N_DAYS = 5;
$current_sum = array_sum(array_map(fn($x) => $x["price"], array_slice($array, 0, $OVER_N_DAYS)));

Následně vyplníme první prvek toho pole, pro který jsme schopni vypočítat ten průměr (tedy předchází mu těch N dní, ze kterých se průměr chce počítat).

$array[$OVER_N_DAYS - 1]["rolling_mean"] = $current_sum / $OVER_N_DAYS;

No a nyní projedeme zbytek následujících dní. Vždy starou hodnotu odečteme, novou hodnotu přičteme (k té naší sumě) a nakonec přidáme informaci k danému prvku v poli

for ($old = 0, $curr = $OVER_N_DAYS; $curr < count($array); $curr++, $old++) {
    $current_sum -= $array[$old]["price"];
    $current_sum += $array[$curr]["price"];
    $array[$curr]["rolling_mean"] = $current_sum / $OVER_N_DAYS;
}

Hotovo...

Má to tedy předpoklad, že opravdu máš záznamy pro každý den, ignoruje to tedy to políčko date. Také se nepočítají žádné průměry pro těch prvních N dní. Obojí je ale něco, co bys měl být schopen hravě vyřešit.

Celý kód, když ho jen udělám copy-paste těch snippetů nahoře:
$array = array(
    array("price" => 1, "date" => "2020-10-01"),
    array("price" => 2, "date" => "2020-10-02"),
    array("price" => 3, "date" => "2020-10-03"),
    array("price" => 4, "date" => "2020-10-04"),
    array("price" => 5, "date" => "2020-10-05"),
    array("price" => 6, "date" => "2020-10-06"),
    array("price" => 7, "date" => "2020-10-07"),
    array("price" => 8, "date" => "2020-10-08"),
    array("price" => 9, "date" => "2020-10-09"),
    array("price" => 10, "date" => "2020-10-10"),
    array("price" => 11, "date" => "2020-10-11"),
    array("price" => 12, "date" => "2020-10-12"),
    array("price" => 13, "date" => "2020-10-13"),
    array("price" => 14, "date" => "2020-10-14"),
    array("price" => 15, "date" => "2020-10-15"),
);

$OVER_N_DAYS = 5;

$current_sum = array_sum(array_map(fn($x) => $x["price"], array_slice($array, 0, $OVER_N_DAYS)));
$array[$OVER_N_DAYS - 1]["rolling_mean"] = $current_sum / $OVER_N_DAYS;

for ($old = 0, $curr = $OVER_N_DAYS; $curr < count($array); $curr++, $old++) {
    $current_sum -= $array[$old]["price"];
    $current_sum += $array[$curr]["price"];
    $array[$curr]["rolling_mean"] = $current_sum / $OVER_N_DAYS;
}

print_r($array);
Kajman
Profil
Jen poznámka... pokud se ta prvotní data získávájí z databáze a ta databáze podporuje window funkce, tak to lze vypočítat hned i v dotaze na databázi.
dev.mysql.com/doc/refman/8.0/en/window-functions-frames.html
danhill
Profil
RastyAmateur:

Pecka !!! Děkuji.
Jde vidět, že jsem doposud neobjevil zdaleka všechny php funkce s array :) ...
Nicméně v tom řádku :
$current_sum = array_sum(array_map(fn($x) => $x["price"], array_slice($array, 0, $OVER_N_DAYS)));
mi to bleje chybu :
Parse error: syntax error, unexpected '=>' (T_DOUBLE_ARROW), expecting ')'
???
RastyAmateur
Profil
danhill:
Hádám, že to bude verzí PHP? Ta tvoje má evidentně problém s touto anonymní funkcí. Teď už ani nevím, kde jsem to včera vygooglil, teď jsem v rychlosti hledal a možná to je podporované až v PHP 8. Každopádně zkus následující:
$current_sum = array_sum(array_map(function($x) { return $x["price"]; }, array_slice($array, 0, $OVER_N_DAYS)));



Každopádně jak psal Kajman, pokud data taháš z databáze, je možné využít tu window funkci.
danhill
Profil
RastyAmateur:
Jooo, to je vončo, super :)
Ano mám php 7.něco, tak to bylo tím.
Tak mi to vyhovuje, zdroj je z externího JSONu.
Tohle je super, jen mi teď teda v array chybí průměr za prvních $OVER_N_DAYS

Nikdy bych neřekl, že tohle půjde napsat na nějakých 8 řádků ... Klobouček teda ... Díky moc!
RastyAmateur
Profil
danhill:
jen mi teď teda v array chybí průměr za prvních $OVER_N_DAYS
Tady je potřeba si nejprve rozmyslet, jak se to bude počítat. Jako normální průměr, tedy nebudeš mít průměr za 10 dní, ale jen za 1, 2, 3, ..., 10? Nebo když máš počítat průměr za 10 dní, ale máš k dispozici jen řekněme 5 hodnot, tak tam 5x doplníš nějakou pevně danou hodnotu? Podle toho si pak musíš opět v nějakém for-cyklu dopočítat těch prvních N.

Pokud chceš hodnoty do průměru nafalšovat nějakou předem danou hodnotou (např. nulou, průměrem za celou dobu, první hodnotou, apod.), můžeš do toho pole na začátek vložit N "umělých hodnot", něco jako array("date" => NULL, "value": 0), pak použiješ úplně ten stejný algoritmus, jako je výše, a nakonec jen odřízneš prvních N prvků pryč.

Pokud chceš rolling_mean za první den počítat z jednoho dne, za dva dny z dvou dní, za tři dny ze tří dní, atd., tak máš v zásadě dvě možnosti. Buď vytvoříš ještě jeden for-cyklus, který podobným způsobem napočítá hodnoty jen pro těch prvních N dní (bude jednoduše sčítat hodnoty a dělit je počtem prvků) a nebo aktuální algoritmus upravíš, aby si počítal nejen sumu, ale i počet prvků a uděláš si v něm podmínku, která bude kontrolovat, jestli už jsi překročil to N a nebo ne.

Takže si rozmysli, jak chceš vlastně počítat průměr za posledních N dní, když nemáš celých N dní, a podle toho to nějak naimplementuj. Kdybys nevěděl, jak na to, klidně se zase zeptej, ale nejprve to zkus sám.

---

Teď si ještě tak zpětně říkám, že pokud bys v tom for-cyklu nejprve nastavil rolling_mean a až pak přičítal a odečítal, nebudeš muset před for-cyklem explicitně nastavovat tu hodnotu pro $OVER_N_DAYS - 1. Můžeš si to v rámci seberozvoje a výuky zkusit předělat :)
Kajman
Profil
Nebo si zažádat z toho externího JSONu i o těch 30 přecházejících dní. Použít je pro výpočet, ale nezobrazovat v grafu.

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0