manwithplan
manwithplan

Reputation: 47

Group laravel collection by 5 minutes

I have Laravel collection consisting data from 11:47 to 12:17:

Illuminate\Support\Collection Object
(
    [items:protected] => Array

        (
            [2020-03-17 11:47:00] => Array
                (
                    [avg] => 4.0666666666667
                )

            [2020-03-17 11:48:00] => Array
                (
                    [avg] => 4.8095238095238
                )

            ...

            [2020-03-17 12:17:00] => Array
                (
                    [avg] => 1
                )

        )
)

I need help to group this collection by 5 minutes starting from first date. If I try to group it this way:

$groupedBy5Minutes = $collection->groupBy(function ($item, $key) {
    return Carbon::parse($key)->timestamp - Carbon::parse($key)->timestamp % (300);
});

I get result grouped by 5 minutes but starting from 11:45 (11:45-12:15) rather than 11:47 (11:47:12:17).

Upvotes: 0

Views: 1252

Answers (3)

Ezequiel Fernandez
Ezequiel Fernandez

Reputation: 1074

I think this is another way to do that.

// hours grouped by key, in this case every 6 hours.
$intervals = collect([
    5 => [0,1,2,3,4,5],
    11 => [6,7,8,9,10,11],
    17 => [12,13,14,15,16,17],
    23 => [18,19,20,21,22,23],
]);

$balances = $balances
    ->get()
    ->groupBy(function($balance) use ($intervals) {
        $created_at = Carbon::parse($balance->created_at);
        $hour = $created_at->hour;

        $mapped_interval = $intervals
            ->filter(function($collection) use ($hour) {
                return in_array($hour, $collection);
            })
            ->keys()
            ->first();
        
        return $created_at->format("Ymd") . $mapped_interval; // grouping it
    });

Upvotes: 0

KyleK
KyleK

Reputation: 5056

If you just need the values, you can use diffInMinutes:

$first = $collection->keys()->first();

$grouped = $collection->groupBy(fn($item, $key) =>
    (int) floor(Carbon::parse($key)->diffInMinutes($first) / 5)
);

If you need to keep a reference to the start date, then IGP solution is the right approach.

Upvotes: 2

IGP
IGP

Reputation: 15786

First, I reproduced your collection as follows. The avg is a random number

$period = Carbon::parse('2020-03-17 11:47:00')->toPeriod('2020-03-17 12:17:00', 1, 'minutes')->toArray();
/* 
[
    '2020-03-17 11:47:00',
    '2020-03-17 11:48:00',
    ...,
    '2020-03-17 12:17:00'
]
*/

$collection = collect($period)->mapWithKeys(fn($item) =>
    [$item->format('Y-m-d H:i:s') => ['avg' => rand(1,1000)]]
);
/*
Illuminate\Support\Collection {
    all: [    
        '2020-03-17 11:47:00' => ['avg' => 134],
        '2020-03-17 11:48:00' => ['avg' => 545],
        ...,
        '2020-03-17 12:17:00' => ['avg' => 654]
    ]
}
*/

To get what you want, there's a bit of a trick you can use with Carbon's floor functions:

$interval = Carbon::parse($collection->keys()->first())
  ->floorMinutes(5)
  ->diff($collection->keys()->first());

$grouped = $collection->groupBy(fn($item, $key) =>
    Carbon::parse($key)
    ->floorMinutes(5)
    ->add($interval)
    ->format('Y-m-d H:i:s')
);
// Using classic closures
$grouped = $collection->groupBy(function ($item, $key) use ($interval) {
    return Carbon::parse($key)
    ->floorMinutes(5)
    ->add($interval)
    ->format('Y-m-d H:i:s');
});
/*
Illuminate\Support\Collection {
    all: [    
        '2020-03-17 11:47:00' => Illuminate\Support\Collection {
            all: [
                ['avg' => 134],
                ['avg' => 545],
                ['avg' => 232],
                ['avg' => 654],
                ['avg' => 123]
            ]
        },
        '2020-03-17 11:52:00' => Illuminate\Support\Collection {
            all: [
                ['avg' => 463],
                ['avg' => 765],
                ['avg' => 732],
                ['avg' => 464],
                ['avg' => 512]
            ]
        },
        ...,
        '2020-03-17 12:17:00' => Illuminate\Support\Collection {
            all: [
                ['avg' => 873],
                ['avg' => 797],
                ['avg' => 694],
                ['avg' => 754],
                ['avg' => 654]
            ]
        }
    ]
}
*/

Basically we're grouping by an interval of 5 minutes (11:45, 11:50, ..., 12:15) but adding to each key the difference between the first key (11:47) and the floored value to the last 5 minutes (11:45).

11:45 + (11:47 - 11:45) = 11:47
11:50 + (11:47 - 11:45) = 11:52
11:55 + (11:47 - 11:45) = 11:57
12:00 + (11:47 - 11:45) = 12:02

Upvotes: 1

Related Questions