jq: Turn json object values into arrays - arrays

I have got the following array of objects (this is just an excerpt, also the objects are bigger):
[{
"DATE": "10.10.2017 01:00",
"ID": "X",
"VALUE_ONE": 20,
"VALUE_TWO": 5
},
{
"DATE": "10.10.2017 02:00",
"ID": "X",
"VALUE_ONE": 30,
"VALUE_TWO": 7
},
{
"DATE": "10.10.2017 03:00",
"ID": "X",
"VALUE_ONE": 25,
"VALUE_TWO": 2
},
{
"DATE": "10.10.2017 01:00",
"ID": "Y",
"VALUE_ONE": 10,
"VALUE_TWO": 9
},
{
"DATE": "10.10.2017 02:00",
"ID": "Y",
"VALUE_ONE": 20,
"VALUE_TWO": 5
},
{
"DATE": "10.10.2017 03:00",
"ID": "Y",
"VALUE_ONE": 50,
"VALUE_TWO": 5
},
{
"DATE": "10.10.2017 01:00",
"ID": "Z",
"VALUE_ONE": 55,
"VALUE_TWO": 3
},
{
"DATE": "10.10.2017 02:00",
"ID": "Z",
"VALUE_ONE": 60,
"VALUE_TWO": 7
},
{
"DATE": "10.10.2017 03:00",
"ID": "Z",
"VALUE_ONE": 15,
"VALUE_TWO": 7
}
]
To simplify this for a web application, and also to reduce file size, I would like to convert the "VALUE_ONE","VALUE_TWO" and "DATE" values to arrays for each "ID" just like this:
[{
"DATE": ["10.10.2017 01:00", "10.10.2017 02:00", "10.10.2017 03:00"],
"ID": "X",
"VALUE_ONE": [20, 30, 25],
"VALUE_TWO": [5, 7, 2]
},
{
"DATE": ["10.10.2017 01:00", "10.10.2017 02:00", "10.10.2017 03:00"],
"ID": "Y",
"VALUE_ONE": [10, 20, 50],
"VALUE_TWO": [9, 5, 5]
},
{
"DATE": ["10.10.2017 01:00", "10.10.2017 02:00", "10.10.2017 03:00"],
"ID": "Z",
"VALUE_ONE": [55, 60, 15],
"VALUE_TWO": [3, 7, 7]
}
]
Here it is important that you need to be able find the values that are linked to a certain time (date). As the input values for "DATE" are consecutive, you most probably do not need the DATE value anymore to find the requested "VALUE.." value. You can probably just use the index of the array for that (index=0 is always 10.10.2017 01:00, index=1 is ... 02:00 etc.).
Is it possible to do it like that? This would keep the file size even smaller.
Thanks!

With 2-step reduce(it doesn't look beautiful but works):
jq 'reduce group_by(.ID)[] as $a ([]; . + [ reduce $a[] as $o
({"DATE":[],"VALUE_ONE":[],"VALUE_TWO":[]};
.DATE |= .+ [$o.DATE] | .ID = $o.ID |.VALUE_ONE |= .+ [$o.VALUE_ONE]
| .VALUE_TWO |= .+ [$o.VALUE_TWO]) ] )' input.json
The output:
[
{
"DATE": [
"10.10.2017 01:00",
"10.10.2017 02:00",
"10.10.2017 03:00"
],
"VALUE_ONE": [
20,
30,
25
],
"VALUE_TWO": [
5,
7,
2
],
"ID": "X"
},
{
"DATE": [
"10.10.2017 01:00",
"10.10.2017 02:00",
"10.10.2017 03:00"
],
"VALUE_ONE": [
10,
20,
50
],
"VALUE_TWO": [
9,
5,
5
],
"ID": "Y"
},
{
"DATE": [
"10.10.2017 01:00",
"10.10.2017 02:00",
"10.10.2017 03:00"
],
"VALUE_ONE": [
55,
60,
15
],
"VALUE_TWO": [
3,
7,
7
],
"ID": "Z"
}
]

The following solution avoids group_by for two reasons:
efficiency
the sort used by group_by in jq version 1.5 might not be stable, which complicates things.
Instead we use bucketize defined as follows:
def bucketize(f): reduce .[] as $x ({}; .[$x|f] += [$x] );
To keep things simple, we will also define the following helper function:
# compactify an array with a single ID
def compact:
. as $in
| reduce (.[0]|keys_unsorted[]) as $key ({};
. + {($key): $in|map(.[$key])})
+ {"ID": .[0].ID}
;
Solution
[bucketize(.ID)[] | compact]
This will ensure things are OK even if the set of dates differ across IDs, and even if the JSON objects are not grouped initially by date.
(If you want to drop "DATE" altogether in the final results, then replace the call to compact by compact | del(.DATE) in the line above.)
Output
[
{
"DATE": [
"10.10.2017 01:00",
"10.10.2017 02:00",
"10.10.2017 03:00"
],
"ID": "X",
"VALUE_ONE": [
20,
30,
25
],
"VALUE_TWO": [
5,
7,
2
]
},
{
"DATE": [
"10.10.2017 01:00",
"10.10.2017 02:00",
"10.10.2017 03:00"
],
"ID": "Y",
"VALUE_ONE": [
10,
20,
50
],
"VALUE_TWO": [
9,
5,
5
]
},
{
"DATE": [
"10.10.2017 01:00",
"10.10.2017 02:00",
"10.10.2017 03:00"
],
"ID": "Z",
"VALUE_ONE": [
55,
60,
15
],
"VALUE_TWO": [
3,
7,
7
]
}
]

Here is a solution using reduce, setpath, getpath, del and symbolic variable destructuring. It will collect all the values for keys other than ID and DATE (eliminating the need to hardcode VALUE_ONE, etc.) in parallel arrays.
reduce (.[] | [.ID, .DATE, del(.ID,.DATE)]) as [$id,$date,$v] ({};
(getpath([$id, "DATE"])|length) as $idx
| setpath([$id, "ID"]; $id)
| setpath([$id, "DATE", $idx]; $date)
| reduce ($v|keys[]) as $k (.; setpath([$id, $k, $idx]; $v[$k]))
)
| map(.)
Try it online!

If your data set is small enough, you could just group them up by id and map to the desired results. It won't be super efficient compared to a streaming solution, but will be the simplest to implement using builtins.
group_by(.ID) | map({
DATE: map(.DATE),
ID: .[0].ID,
VALUE_ONE: map(.VALUE_ONE),
VALUE_TWO: map(.VALUE_TWO)
})

Related

Vega Lite - set parameter array max value based on data

I am relatively new to Vega Lite and have a question that I'm hoping is fairly straightforward.
I have a parameter array called myExtent that I've hard coded to [0, 6]. I'd like to be able to set the upper value of the array based on the data. Here, because the 4th row has "flag" = 1, I'd like to set the upper limit to the "score" for that row, or 6. So,
{"name": "myExtent", "value": [0, (value of score for the row in the dataset where flag = 1)]}
Is something like this possible, or is there an alternative way I should be thinking about this?
`
{"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"params": [
{"name": "myExtent", "value": [0, 6]}
],
"data": {
"values": [
{"game": 1, "score": 2, "flag": 0},
{"game": 2, "score": 4, "flag": 0},
{"game": 3, "score": 5, "flag": 0},
{"game": 4, "score": 6, "flag": 1},
{"game": 5, "score": 9, "flag": 0}
]
},
"mark": {"type": "area"},
"transform": [
{
"density": "score",
"extent": {"signal": "myExtent"}
}
],
"encoding": {
"x": {"field": "value", "type": "quantitative", "scale": {"domain": [0, 10]}},
"y": {"field": "density", "type": "quantitative"}
}
}
`
Just to get started, I have tried something like this:
`
"params": [
{"name": "upperLimit", "value": 6},
{"name": "myExtent", "value": [0, {"expr": "upperLimit"}]}
],
`
However, that (a) doesn't seem to work and (b) doesn't (yet) get at how to set the upperLimit parameter to the score for row 4.
What is the logic behind providing a dynamic extent and not letting it calculate from the whole dataset? This is quite difficult to do in VL and you probably need Vega. Having said that, if you can add a column with the max extent repeated for the whole dataset, it can be achieved. I have repurposed the flag column here to do what you want.
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"params": [
{"name": "val", "expr": "data('source_0')[0]['flag']"},
{"name": "myExtent", "expr": "[0,val]"}
],
"data": {
"values": [
{"game": 1, "score": 2, "flag": 6},
{"game": 2, "score": 4, "flag": 6},
{"game": 3, "score": 5, "flag": 6},
{"game": 4, "score": 6, "flag": 6},
{"game": 5, "score": 9, "flag": 6}
]
},
"mark": {"type": "area"},
"transform": [
{"calculate": "datum.flag==1?datum.score:0", "as": "new"},
{"joinaggregate": [{"op": "max", "field": "new", "as": "test"}]},
{"density": "score", "extent": {"signal": "myExtent"}}
],
"encoding": {
"x": {
"field": "value",
"type": "quantitative",
"scale": {"domain": [0, 10]}
},
"y": {"field": "density", "type": "quantitative"}
}
}
With David Bacci's excellent guidance (thanks again!), I was able to build a solution that seems to accomplish what I was after. By flagging one of the rows with flag = 1, the user can see a density graph that helps them understand the percentile rank of the score in question (in this case, they're interested in game 4):
{"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"params": [
{"name": "upperLimit", "expr": "data('data_1')[0]['maxScore']"},
{"name": "myExtent", "expr": "[0,upperLimit]"}
],
"data": {
"name": "rawData",
"values": [
{"game": 1, "score": 2, "flag": 0},
{"game": 2, "score": 4, "flag": 0},
{"game": 3, "score": 5, "flag": 0},
{"game": 4, "score": 6, "flag": 1},
{"game": 5, "score": 9, "flag": 0}
]
},
"layer": [
{ // first layer to perform the transform without density
"mark": {"type": "area", "opacity": 0.1},
"transform": [
{"calculate": "datum.flag==1?datum.score:0", "as": "myScore"},
{"joinaggregate": [{"op": "max", "field": "myScore", "as": "maxScore"}]}
],
"encoding": {
"x": {"field": "game", "type": "quantitative", "scale": {"domain": [0, 10]}, "title": null}
}
},
{ // second layer to show whole sample with low opacity
"mark": {"type": "area", "opacity": 0.1},
"transform": [{"density": "score", "extent": [0, 10]}],
"encoding": {
"x": {
"field": "value",
"type": "quantitative"
},
"y": {"field": "density", "type": "quantitative", "title": null}
}
},
{ // third layer to show partial sample based on flagged row
"mark": {"type": "area"},
"transform": [
{"density": "score", "extent": {"signal": "myExtent"}}
],
"encoding": {
"x": {
"field": "value",
"type": "quantitative"
},
"y": {"field": "density", "type": "quantitative"}
}
}
]
}
This approach creates 3 layers, the first with a transform that David suggested to find the score in question, but without the density function. The second layer has a density function that shows the whole sample and the third layer shows just the part of the sample of interest. The parameters then refer not to the raw data source, but to data_1, which is the output of the first transform. Thanks to the online Vega Lite editor for helping me figure that bit out. But, especially thanks to David Bacci for pointing me in the right direction!

Loop within a Loop when condition satisfies should return newsingleArray

I have two loops, when the condition is satisfied I want to return single array.
My configuration array is as follows
"configuration": [
{
"position": "O",
"side": "L",
"type": 21,
"wheel": 1,
"wheels": 20
},
{
"position": "I",
"side": "L",
"type": 21,
"wheel": 2,
"wheels": 20
},
]
My sensor array is as follows
“Sensor”: [
{
"pressure": 8126,
"sub_item": "1",
"temp": 16,
"time": 1572243074,
},
{
"pressure": 8205,
"sub_item": "10",
"temp": 18.3,
"time": 1572243092,
},
]
I am looping through sensor array from configuration array and making a condition here
let finalarray = []
configuration.forEach((e1) => sensorData.forEach((e2) => {
if (e1.wheel == e2.sub_item) {
finalarray.push(e1)
finalarray.push(e2)
console.log(JSON.stringify(finalarray))
}
}
))
I am expecting final array should be single array with both configuration and sensor array but I am receiving two different arrays.
I am guessing you wish to merge your object values instead of pushing them separately in the array for which you can use the spread operator syntax
const configuration = [
{
"position": "O",
"side": "L",
"type": 21,
"wheel": 1,
"wheels": 20
},
{
"position": "I",
"side": "L",
"type": 21,
"wheel": 2,
"wheels": 20
},
]
const sensorData = [
{
"pressure": 8126,
"sub_item": "1",
"temp": 16,
"time": 1572243074,
},
{
"pressure": 8205,
"sub_item": "10",
"temp": 18.3,
"time": 1572243092,
},
]
let finalarray = []
configuration.forEach((e1) => sensorData.forEach((e2) => {
if (e1.wheel == e2.sub_item) {
finalarray.push({...e1, ...e2})
}
}))
console.log(finalarray)

Drawing a bar chart by passing values from a json array through angularjs

I have a JSON array as follows
{
"id": "00000005",
"Name": "Test5",
"hours": 7.5,
"day": 1
},
{
"id": "00000005",
"Name": "Test5",
"hours": 2,
"day": 2
},
{
"id": "00000005",
"Name": "Test5",
"hours": 3,
"day": 3
},
{
"id": "00000005",
"Name": "Test5",
"hours": 3,
"day": 4
},
{
"id": "00000004",
"Name": "Test4",
"hours": 1,
"day": 1
},
{
"id": "00000004",
"Name": "Test4",
"hours": 4,
"day": 2
},
{
"id": "00000004",
"Name": "Test4",
"hours": 4,
"day": 3
},
{
"id": "00000003",
"Name": "Test3",
"hours": 7.5,
"day": 1
},
{
"id": "00000003",
"Name": "Test3",
"hours": 6,
"day": 2
},
{
"id": "00000003",
"Name": "Test3",
"hours": 4,
"day": 3
},
{
"id": "00000003",
"Name": "Test3",
"hours": 5,
"day": 4
}
By using the above json array I want to draw a bar chart grouped by id and day. That is I need to the graph to be drawn for id 00000005,00000004,00000003 in day 1 and id 00000005,00000004,00000003 in day 2 and id 00000005,00000004,00000003 in day 3 and id 00000005,00000004,00000003 in day 4.
I know the basic code for drawing a bar chart using angular-chart.js and chart.js. But I can't understand how should I pass my array values to $scope.labels and $scope.data.
Basic code for bar chart
angular.module("app", ["chart.js"]).controller("BarCtrl", function ($scope) {
$scope.labels = ['2006', '2007', '2008', '2009', '2010', '2011', '2012'];
$scope.series = ['Series A', 'Series B'];
$scope.data = [
[65, 59, 80, 81, 56, 55, 40],
[28, 48, 40, 19, 86, 27, 90]
];
})
UPDATE
I managed to group the array according to the day and following is the arranged array
"data": {
"1": [
{
"id": "00000005",
"Name": "Test5",
"hours": 7.5,
"day": 1
},
{
"id": "00000004",
"Name": "Test4",
"hours": 1,
"day": 1
},
{
"id": "00000003",
"Name": "Test3",
"hours": 7.5,
"day": 1
}
],
"2": [
{
"id": "00000005",
"Name": "Test5",
"hours": 2,
"day": 2
},
{
"id": "00000004",
"Name": "Test4",
"hours": 4,
"day": 2
},
{
"id": "00000003",
"Name": "Test3",
"hours": 6,
"day": 2
}
],
"3": [
{
"id": "00000005",
"Name": "Test5",
"hours": 3,
"day": 3
},
{
"id": "00000004",
"Name": "Test4",
"hours": 4,
"day": 3
},
{
"id": "00000003",
"Name": "Test3",
"hours": 4,
"day": 3
},
],
"4": [
{
"id": "00000005",
"Name": "Test5",
"hours": 3,
"day": 4
},
{
"id": "00000003",
"Name": "Test3",
"hours": 5,
"day": 4
}
]
}
}
Now how can I assign these values to $scope.data and $scope.labels ?
If you take a run at the basic code, applied to your data, do you want something like this (if I were reporting on this data):
angular.module("app", ["chart.js"]).controller("BarCtrl", function ($scope) {
$scope.labels = ['00000005', '00000004', '00000003'];
$scope.series = ['Day 1', 'Day 2', 'Day 3', 'Day 4'];
$scope.data = [
[7.5, 1, 7.5],
[2, 4, 6],
[3, 4, 4],
[3, ?, 5],
];
})
Note entirely sure how you want to handle missing data, you'll have to play around with a combination of 0 or undefined within the chart. If you have issues splitting the data in this way, or this isn't what you're looking for, will need more details.
https://plnkr.co/edit/ouHu5R0UbbkxyDfUerru?p=preview

Resultset mapper or formatter in Cakephp 3

I have 2 Collections/ Result sets the first one is products and second one is sizes
"products": [
{
"category_id": 5,
"id": 5,
"code": "A",
"name": "Pizzabrot",
"description": "",
"product_prices": [
{
"product_id": 5,
"price": 2.5,
"size_id": 15
},
{
"product_id": 5,
"price": 3.5,
"size_id": 16
}
]
},
{
"category_id": 5,
"id": 6,
"code": "B",
"name": "Pizzabrot mit Knoblauch",
"description": "",
"product_prices": [
{
"product_id": 6,
"price": 3,
"size_id": 15
},
{
"product_id": 6,
"price": 4,
"size_id": 16
}
]
}]
AND
"sizes": [
{
"id": 15,
"name": "Klein",
"category_id": 5
},
{
"id": 16,
"name": "Gro\u00df",
"category_id": 5
}
]
I want to replace every product_prices.size_id with it's name from sizes Collection
From what I can see it would probably be better to associate ProductPrices and Sizes and fetch them with your Products.
If this does not fit your needs for some reason, you could find the sizes by using a find('list') (see Docs) like this:
$query = $this->Sizes->find('list', [
'keyField' => 'id',
'valueField' => 'name'
]);
$sizes = $query->toArray();
Then loop through the products and its product_prices and do something like.
$product_price->size_id = $sizes[$product_price->size_id];
Please prefer the first solution. ;-)

Lodash for reagrup complex Array[object]

I'm a newbie but i thing this is for Senior Lodash Programmer
I'm new to Lodash and trying to solve this problem but could find a good way to do it.
I have an array of orders and an array of lines with one ticket into each order.
The data is structured as below:
[{
"id": 201,
"order": "Buyer 1",
"lines": ▿[
▿{
"id": 1,
"ticket": ▿{
"id": 151,
"event": ▿{
"id": 31,
"name": "Event 1"
},
"name": "Event 1 Ticket 1",
"price": 39,
"minimum": 1,
"maximum": 5
},
"quantity": 1
},
▿{
"id": 2,
"ticket": {
"id": 152,
"event": {
"id": 31,
"name": "Event 1"
},
"name": "Event 1 Ticket 2",
"price": 60,
"minimum": 2,
"maximum": 4
},
"quantity": 5
},
▿{
"id": 3,
"ticket": {
"id": 153,
"event": {
"id": 33,
"name": "Event 1"
},
"name": "Event 3 Ticket 2",
"price": 60,
"minimum": 2,
"maximum": 4
},
"quantity": 5
}
],
"created_at": "2016-12-22T17:58:27+0000"
},
...]
I need to group all orders by event name and need the next structure:
[{
"event_id": 31,
"event_name":"Event 1",
"total_tickets": 6,
"total_sold": 339
"orders": [
{
"id": 201,
"order": "Buyer 1"
},
....
]},
{
"event_id": 33,
"event_name":"Event 3",
"total_tickets": 5,
"total_sold": 300
"orders":[
{
"id": 201,
"order": "Buyer 1"
}
]},
....
]
"total_tickets" -> sum of order' tickets (quantity) of event
"total_sold" -> sum of order' (price* quantity) of event
Any help will by very usefull
Take a look at _.reduce (or even vainilla JS Array.reduce)
As a friendly advice, try to imagine and sketch - in your mind, or a paper - what exactly needs be done with that data and then start looking for the tools to do it, not the other way around.

Resources