data aggregation by time window with JS - javascript

I built a simple NodeJS server that emits client events. An event is a JSON object that looks like this:
{
"date": "2019-12-12T09:55:05.679Z",
"clientId": "Client01",
"ip": "10.1.1.1",
"event": "someEvent"
}
An event can be emitted anytime. I get many messages with different timestamps (date).
Currently, I store all the data in memory.
How I can aggregate the client events by date?
let's say in 15-minute bulks, so I end up with a total number of client events for each time frame, for example:
const timestamps = ["2019-12-12T09:00:00.000Z", "2019-12-12T09:15:00.000Z", "2019-12-12T09:30:00.000Z"];
const totalNumEvents = [50, 27, 82];
const clientIds = ["Client01", "Client02", "Client03"];
Sorry if the question is too generic, I tried to look for direction by googling but couldn't find any solution without using a framework / DB like MongoDB.
So far, what I did is, creating an object for each clientId and push the event into it. (I have a callback for each event)
const onEventArrived = (client) => {
if (!clients[client.clientId]) {
console.log(`[New] Client Added - ${client.clientId}`);
clients[client.clientId] = [{ date, client}];
} else {
console.log(`[Exist] New Client Message for ${client.clientId}`);
clients[client.clientId].push({ date, client});
}
});
Back to question, I have all the events for each client, but how do I aggregate the random times into fixed 15 minute windows?

You will need to store the events as a flat object in an array and use Array.reduce() or lodash's groupBy() to group the array of event objects by any of the desired fields.
Here's an example using your supplied event example
Your incoming event looks like this:
{
"date": "2019-12-12T09:55:05.679Z",
"clientId": "Client01",
"ip": "10.1.1.1",
"event": "someEvent"
}
Now when this event is received, here's what happens:
const events = []
const onEventArrived = (event) => {
events = [...events, event ]
});
Next you run a group by as follows:
Array.prototype.groupBy = function(k) {
return this.reduce((acc, item) => ((acc[item[k]] = [...(acc[item[k]] || []), item]), acc),{});
};
events.groupBy("date")
// OR
events.groupBy("clientId")
You can extend this to fit your use case
Reference: How to group an array of objects by key

An idea can be to:
associate an id to each date (a quarterId)
get the minQuarterId, the maxQuarterId and fill those in between with 0
const bulks = {}
const clientIds = new Set
function onev(ev) {
clientIds.add(ev.clientId)
const quarters = ev.date.getTime() / (15 * 60 * 1000)
const quarterId = Math.floor(quarters)
bulks[quarterId] = (bulks[quarterId] || 0) + 1
}
function restitute() {
if(!Object.keys(bulks).length){ return { ts: [], nevs: [] } }
const ts = []
const nevs = []
const {min, max} = Object.keys(bulks).reduce((acc, k) => {
const pk = parseInt(k)
if(pk < acc.min){
acc.min = pk
}
if (pk > acc.max) {
acc.max = pk
}
return acc
}, {min:1e15, max:0})
for(let i = min; i <= max; ++i){
ts.push(new Date(i * 15 * 60 * 1000))
nevs.push(bulks[i] || 0) // fill with 0 if no quarterId "i" exists
}
return {ts, nevs}
}
onev({clientId: 1, date:new Date(2019, 11, 12, 13, 24)})
onev({clientId: 2, date:new Date(2019, 11, 12, 13, 28)})
onev({clientId: 1, date:new Date(2019, 11, 12, 13, 29, 30)})
onev({clientId: 1, date:new Date(2019, 11, 12, 13, 31, 30), newquarter:1})
onev({clientId: 4, date:new Date(2019, 11, 12, 13, 29, 30)})
onev({clientId: 4, date:new Date(2019, 11, 12, 12, 29, 30), emptyquarters:1})
onev({clientId: 4, date:new Date(2019, 11, 12, 12, 45, 30), indapast:1})
console.log(restitute())

Related

Format by range-dates gap using map

I have a "format" array that's used in a map function to return an array of objects with start and end dates.
This format array contains the group of dates that belong to the same object.
let format = [3, 3, 1, 5, 4, 4, 3, 5, 13, 10, 3, 5, 5, 2, 2, 10];
So this way we know the first 3 dates same group, next 3 dates, same group, next date single group date, etc.
My issue/problem: is when there's a group of dates with no-lineal dates (for example 05, 06, 12, 13).
With my actual function, is returning an object with
Start: 05
End: 13
But this isn't correct, because we are counting all the days in the middle between 5 and 13. What I would like to do would create two objects for this case:
{
"start": 05,
"end": 06
},
{
"start": 12,
"end": 13
}
In my code, you can see this behavior with the last group of dates (10) (last object).
Is there any way to "check" for the range-dates" before creating the object? Should I add a .map inside the current .map to get 3 sets from the last 10
{
"2022-01-05T04:00:00.000Z", // start
"2022-01-06T04:00:00.000Z",
"2022-01-07T04:00:00.000Z" // end
},
{
"2022-01-10T04:00:00.000Z", // start
"2022-01-11T04:00:00.000Z",
"2022-01-12T04:00:00.000Z",
"2022-01-13T04:00:00.000Z",
"2022-01-14T04:00:00.000Z" // end
},
{
"2022-01-17T04:00:00.000Z", // start
"2022-01-18T04:00:00.000Z" // end
}
Current code:
let format = [3, 3, 1, 5, 4, 4, 3, 5, 13, 10, 3, 5, 5, 2, 2, 10];
let dates = [
"2021-10-04T04:00:00.000Z",
"2021-10-06T04:00:00.000Z",
"2021-10-07T04:00:00.000Z",
"2021-10-13T04:00:00.000Z",
"2021-10-14T04:00:00.000Z",
"2021-10-15T04:00:00.000Z",
"2021-10-15T04:00:00.000Z",
"2021-10-17T22:00:00.000Z",
"2021-10-18T22:00:00.000Z",
"2021-10-19T22:00:00.000Z",
"2021-10-20T22:00:00.000Z",
"2021-10-21T22:00:00.000Z",
"2021-10-17T22:00:00.000Z",
"2021-10-18T22:00:00.000Z",
"2021-10-19T22:00:00.000Z",
"2021-10-20T22:00:00.000Z",
"2021-10-19T04:00:00.000Z",
"2021-10-20T04:00:00.000Z",
"2021-10-21T04:00:00.000Z",
"2021-10-22T04:00:00.000Z",
"2021-10-19T04:00:00.000Z",
"2021-10-20T04:00:00.000Z",
"2021-10-21T04:00:00.000Z",
"2021-10-25T04:00:00.000Z",
"2021-10-26T04:00:00.000Z",
"2021-10-27T04:00:00.000Z",
"2021-10-28T04:00:00.000Z",
"2021-10-29T04:00:00.000Z",
"2021-10-25T04:00:00.000Z",
"2021-10-26T04:00:00.000Z",
"2021-10-27T04:00:00.000Z",
"2021-10-28T04:00:00.000Z",
"2021-10-29T04:00:00.000Z",
"2021-11-01T04:00:00.000Z",
"2021-11-02T04:00:00.000Z",
"2021-11-03T04:00:00.000Z",
"2021-11-04T04:00:00.000Z",
"2021-11-05T04:00:00.000Z",
"2021-11-08T04:00:00.000Z",
"2021-11-09T04:00:00.000Z",
"2021-11-10T04:00:00.000Z",
"2021-11-01T04:00:00.000Z",
"2021-11-02T04:00:00.000Z",
"2021-11-03T04:00:00.000Z",
"2021-11-04T04:00:00.000Z",
"2021-11-05T04:00:00.000Z",
"2021-11-08T04:00:00.000Z",
"2021-11-09T04:00:00.000Z",
"2021-11-10T04:00:00.000Z",
"2021-11-11T04:00:00.000Z",
"2021-11-12T04:00:00.000Z",
"2021-11-11T04:00:00.000Z",
"2021-11-12T04:00:00.000Z",
"2021-11-13T04:00:00.000Z",
"2021-11-15T04:00:00.000Z",
"2021-11-16T04:00:00.000Z",
"2021-11-17T04:00:00.000Z",
"2021-11-18T04:00:00.000Z",
"2021-11-19T04:00:00.000Z",
"2021-11-16T04:00:00.000Z",
"2021-11-17T04:00:00.000Z",
"2021-11-18T04:00:00.000Z",
"2021-11-19T04:00:00.000Z",
"2021-11-20T04:00:00.000Z",
"2021-11-23T04:00:00.000Z",
"2021-11-24T04:00:00.000Z",
"2021-11-23T04:00:00.000Z",
"2021-11-24T04:00:00.000Z",
"2022-01-05T04:00:00.000Z",
"2022-01-06T04:00:00.000Z",
"2022-01-07T04:00:00.000Z",
"2022-01-10T04:00:00.000Z",
"2022-01-11T04:00:00.000Z",
"2022-01-12T04:00:00.000Z",
"2022-01-13T04:00:00.000Z",
"2022-01-14T04:00:00.000Z",
"2022-01-17T04:00:00.000Z",
"2022-01-18T04:00:00.000Z"
];
const res = format.map(num => {
const arr = dates.splice(0,num)
const start = arr.shift();
const end = arr.length === 0 ? start : arr.pop()
return {
start: start,
end: end
}
})
console.log(res);

How can I push data to an array with fixed length in js

I have an array of object something like this.
[
{
channelName: "WhatsApp"
count: 1
date: "2021-06-05"
},{
channelName: "RCS"
count: 1
date: "2021-06-09"
}
]
There are two types of channel names 1. WhatsApp and 2nd are RCS. I want to filter out count with specific channel names and store it in a separate array. But the problem here is I want both the array length should be the same. If there is data for WhatsApp then it will add the count otherwise it will add 0 in place of it.
For that, I did something like this but this does not work .
const filterData = (data: any) => {
const category: any = [];
const whatsAppCount: any = [];
const rcsCount: any = [];
data.filter((item: any, i: number) => {
if (item.channelName === "WhatsApp") {
whatsAppCount[i] = item.count;
} else if (item.channelName === "RCS") {
rcsCount[i] = item.count;
}
category.push(item.date);
});
setGraphData({
category: category,
whatsApp: whatsAppCount,
rcs: rcsCount,
});
console.log("handleRun", { category, whatsAppCount, rcsCount });
};
Here the console log gives something like this.
whatsAppCount: [1, 2, 13, 21, empty × 2, 8, 5, empty, 18, empty, 12, 4]
rcsCount: [empty × 4, 1, 12, empty × 2, 1, empty, 8]
Here in the place of empty, I want 0. I am not sure how to do that any help would be great.
When you create the arrays, but before populating them, there are two functions that can help with initialization:
// create an array with 10 slots preallocated but empty (not `undefined`)
let arr = new Array(10);
// set all allocated slots to a value (`0` in this case)
arr = arr.fill(0);
Since you know the lengths you want ahead of time, you can use that to pre-size the arrays on construction. Then use .fill to initialize the values to 0. Once, that's done, you can continue with your counting and updating the arrays.
Reference:
Array constructor
Array.prototype.fill()
I would suggest you use the map-function, mapping the unwanted values to undefined, letting the other values "pass through" (unmodified), eg.:
const filtered = data.map((each) => {
if (wantToKeep) {
return each;
} else {
return undefined;
}
});
Note, this is not the exact solution - but a general idea.
You can use forEach and push(0) for the empty records.
const data = [
{
channelName: "WhatsApp",
count: 1,
date: "2021-06-05",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-01",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-06",
},
{
channelName: "WhatsApp",
count: 5,
date: "2021-06-11",
},
{
channelName: "WhatsApp",
count: 7,
date: "2021-06-23",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-09",
},
];
const category = [];
const whatsAppCount = [];
const rcsCount = [];
data.forEach(x => {
if (x.channelName === "WhatsApp") {
whatsAppCount.push(x.count);
rcsCount.push(0);
} else if (x.channelName === "RCS") {
whatsAppCount.push(0);
rcsCount.push(x.count);
}
category.push(x.date);
});
console.log({ whatsAppCount });
console.log({ rcsCount });
console.log({ category });

Best way to tally/find quantity all elements in an object with a given ID?

The concept is simple, I have two datasets. One is "classes" and one is "attendees". Each Class has a unique ID and some other stuff, like so (the actual data sets are much bigger):
[{class_id: 9, class_cap: 50, class_name: 'Science'},{class_id: 10, class_cap: 30, class_name: 'French'}]
Attendees is similar, where class_id represents the class they are attending:
[{attendee_id: 55, class_id: 9, attendee_name: 'Jack'},{attendee_id: 56, class_id: 10, attendee_name: 'Jill'}]
What I need to do is be able to find out the quantity of students that will be attending each class. I have tried to do it using a for loop and if statement, in an attempt to create a new array for every class which stores the attendee ID of every student attending it (then I can just use length to get quantity of students), but it's not working properly and I can't wrap my head around the correct implementation. The idea is to iterate through every class, and then for every class, iterate through all the attendees to find ones with the same class_id, and then add it to the attendeeArray.
for (let i = 0; i < classes.length; i++){
let classID = classes[i].class_id;
let attendeeArray = [];
for (let d = 0; d < attendees.length; d++){
if(attendees[i].class_id == classID){
attendeeArray[i] = attendees[i].attendee_id;
}
console.log(`Attendance number for class ID ${classID} is ${attendeeArray.length}`)
}
Is there a better way to do this? Any method is fine. Thanks
Try this:
const classes = [{class_id: 9, class_cap: 50, class_name: 'Science'},{class_id: 10, class_cap: 30, class_name: 'French'}]
const students = [{attendee_id: 55, class_id: 9, attendee_name: 'Jack'},{attendee_id: 56, class_id: 10, attendee_name: 'Jill'}]
const classOccupancy = classes.map(oneClass => ({
class_id:oneClass.class_id,
number_attendees: students.filter(student => student.class_id === oneClass.class_id).length
}))
console.log(
`Attendance number for class ID ${classOccupancy[0].class_id} is ${classOccupancy[0].number_attendees}`
)
A bit to late:
let classes = [{ class_id: 9, class_cap: 50, class_name: 'Science' }, { class_id: 10, class_cap: 30, class_name: 'French' }];
let attendees = [{ attendee_id: 55, class_id: 9, attendee_name: 'Jack' }, { attendee_id: 56, class_id: 10, attendee_name: 'Jill' }];
let attClassesCount = classes.map(c => {
let attCount = attendees.filter(a => a.class_id === c.class_id).length;
return { class_id: c.class_id, attendees_count: attCount }
})
console.log(attClassesCount);

Getting function to return the 'new' object with the highest value from an array

First time poster here and JavaScript beginner. I'm trying to get a function to return the object with the higher traffic value. I scower the web for how to return the max value so I know all about Math.max at this point, but still I haven't been able to make it work as intended. Here's an example of my code:
function mostBusyDays(week) {
var week = [
new Weekday("Monday", 11),
new Weekday("Tuesday", 14),
new Weekday("Wenesday", 19),
new Weekday("Thursday", 21),
new Weekday("Friday", 24),
new Weekday("Saturday", 29),
new Weekday("Sunday", 6),
];
return week;
}
That's the function that I'm trying to implement, here is the prototype from were the new objects of weekday are been created:
function Weekday(name, traffic) {
this.name = name;
this.traffic = traffic;
}
Calling the function on the console as it is shown return week does return an array as intended, so I at least know that the code is working but for the life of me I just not been able to make it return only the weekday with the highest traffic. I'll appreciate any enlightenment you have to offer me
You can just try with the Array.reduce function:
function mostBusyDays() {
var week = [
new Weekday("Monday", 11),
new Weekday("Tuesday", 14),
new Weekday("Wenesday", 19),
new Weekday("Thursday", 21),
new Weekday("Friday", 24),
new Weekday("Saturday", 29),
new Weekday("Sunday", 6),
];
return week.reduce((a,b)=>a.traffic>b.traffic?a:b);
}
function Weekday(name, traffic) {
this.name = name;
this.traffic = traffic;
}
console.log(mostBusyDays());
See Array.prototype.reduce
Please try the following example
The idea is to order week by traffic in descending order and then obtain the first element of the array that would be the one with the highest traffic value.
function mostBusyDays(week) {
var week = [
new Weekday("Monday", 11),
new Weekday("Tuesday", 14),
new Weekday("Wenesday", 19),
new Weekday("Thursday", 21),
new Weekday("Friday", 24),
new Weekday("Saturday", 29),
new Weekday("Sunday", 6),
];
week.sort((a, b) => b.traffic - a.traffic);
return week[0].name;
}
function Weekday(name, traffic) {
this.name = name;
this.traffic = traffic;
}
console.log(mostBusyDays());
See
Array.prototype.sort()
yes array reduce, but be careful to use a copy of the item if you need to preserve the original
const week =
[ { name: 'Monday', traffic: 11 }
, { name: 'Tuesday', traffic: 14 }
, { name: 'Wenesday', traffic: 19 }
, { name: 'Thursday', traffic: 21 }
, { name: 'Friday', traffic: 24 }
, { name: 'Saturday', traffic: 29 }
, { name: 'Sunday', traffic: 6 }
]
const higher = week.reduce((a,c)=>
{
if (!a || a.traffic < c.traffic) a={...c}
return a
},null)
console.log( JSON.stringify( higher ))

Efficiently grouping data based on date and day-of-week

Given a list of items sorted in descending order by date (most recent date at index 0). Pls note that we may not have data for all dates.
[
{price: 10, dt: "2017-02-13"},
{price: 25, dt: "2017-02-05"},
{price: 15, dt: "2017-01-15"},
...
..
]
I am attempting to group this data into weeks which I was able to do successfully using a "while" loop.
See Plnkr here
I am not currently able to use constructs like _.groupBy() cause the grouping clause is constantly moving .. (we we have to group based on weeks and a week could be Mn-Sun or Tues-Mn etc)
is there a more elegant solution to this ?
This code will create array of arrays representing each week:
const data = [
{ price: 10, dt: "2017-02-13" },
{ price: 10, dt: "2017-02-12" },
{ price: 10, dt: "2017-02-11" },
{ price: 25, dt: "2017-02-05" },
{ price: 15, dt: "2017-01-15" }
]
const group = (data) => {
let date = new Date(data[0].dt)
date.setTime -= date.getDay() * 24 * 60 * 60 * 1000
let grup = []
let newData = []
for (let i in data) {
const dif = Math.floor(((date - new Date(data[i].dt)) / 24 / 1000 / 60 / 60) / 7)
if (newData[dif]) {
newData[dif].push(data[i])
} else {
newData[dif] = [data[i]]
}
}
data.push(grup)
return newData
}
console.log(group(data))
note if data of week not found value would be undefined

Categories

Resources