Crossfilter: filtering by X and Y for scatter plot - crossfilter

Assuming I have an array of objects like that:
[{xVal: 9.7, yVal: 100},
{xVal: 12.3, yVal: 40},
{xVal: 12.4, yVal: 60}, ...]
I would like to filter it and reduce its size significantly to use in a scatter plot. I have been trying out some things but was left a bit confused about correct practices but also the API documentation at some point. From what I understand, filters stack, so I tried this:
let xDim: Dimension<ITest, number> = cf.dimension((d: ITest) => d.xVal)
let yDim: Dimension<ITest, number> = cf.dimension((d: ITest) => d.yVal)
yDim.filterRange([40, 80])
let xDimGrouped: any = xDim.group((d: number) => Math.round(d))
My intention is to eventually group based on x and y while also being able to filter it. In the code above, the filter is ignored as soon as it is being grouped (xDim.top(3) would return a filtered array though).
To group by two values I have tried this:
let combDim: Dimension<ITest, string> = cf.dimension((d: ITest) => Math.round(d.xVal)+':'+d.yVal)
let yDim: Dimension<ITest, number> = cf.dimension((d: ITest) => d.yVal)
This works but would require to split all keys in the end to retrieve x and y for the plot. Unless I am missing something. But here the same problem also occurs. Filtering yDim would affect combDim.top(3) but not the group.
The API documentation says that
"a grouping intersects the crossfilter's current filters, except for the associated dimension's filter. Thus, group methods consider only records that satisfy every filter except this dimension's filter. So, if the crossfilter of payments is filtered by type and total, then group by total only observes the filter by type."
Doesn't that mean that a filter on yDim should affect the grouping of combDim?
Edit:
I have created an example of what I meant: https://jsfiddle.net/e98not64/2/
It turns out that all() always returns all members of the group, but updates the value property according to its occurrences with filters applied. I didn't notice this before and thought it is ignoring the filters because I checked for the size of the groups and the array returned :x I could just check if the value is > 0 and split the key value to get x and y. Would that be the right approach/a good way to do it?

Related

Match nodes where all relations satisfy constraints

I'm looking to find nodes that have relations where all relations satisfy that constraint. the exact example is do you have a relation in a list.
the graph is bascially cocktails, with the relations being ingredients. given a list of ingredients i want to know what I can make.
with ['Sweet Vermouth', 'Gin', 'Campari', 'Bourbon'] as list
...
should return Negroni, Boulevardier, ...
I've been finding this tricky because we want to make sure that all relations of a node satisfy the constraint, but the number of nodes could very easily be a subset of the list and not an exact match to the ingredient list.
this is the best I've done so far, and it only works if you have all the ingredients, but nothing extra.
with ['Sweet Vermouth', 'Gin', 'Campari', 'Bourbon'] as list
MATCH (n:Cocktail)-[h:HAS]-(x)
WITH list, count(list) AS lth, n, COLLECT(DISTINCT x.name) AS cx, collect(DISTINCT h) as hh
WHERE ALL (i IN list WHERE i IN cx)
RETURN n
I'ved looked at stackoverflow.com/a/62053139/974731. I don't think it solves my problem
as you can see the addition of Bourbon removes the Negroni, which shouldn't happen since all we've done is add an ingredient to our bar.
This should return all cocktails whose needed ingredients are in the have list.
WITH ['Sweet Vermouth', 'Gin', 'Campari', 'Bourbon'] as have
MATCH (c:Cocktail)-[:HAS]->(x)
WITH have, c, COLLECT(x.name) AS needed
WHERE ALL(n IN needed WHERE n IN have)
RETURN c
Or, if you pass have as a parameter:
MATCH (c:Cocktail)-[:HAS]->(x)
WITH c, COLLECT(x.name) AS needed
WHERE ALL(n IN needed WHERE n IN $have)
RETURN c
It's terribly hacky, but this is where I got
with ['Sweet Vermouth', 'Gin', 'Campari', 'Bourbon'] as list
call {
match (ali:Cocktail)--(ii:Ingredient) //pull all nodes
return ali, count(ii) as needed // get count for needed ingredients
}
MATCH (ali)--(i:Ingredient)
WHERE i.name in list // get ingredients that are in the list
WITH distinct ali.name as name, count(ali.name) as available, needed
WHERE available = needed
RETURN name;

complex reduce sample unclear how the reduce works

Starting with complex reduce sample
I have trimmed it down to a single chart and I am trying to understand how the reduce works
I have made comments in the code that were not in the example denoting what I think is happening based on how I read the docs.
function groupArrayAdd(keyfn) {
var bisect = d3.bisector(keyfn); //set the bisector value function
//elements is the group that we are reducing,item is the current item
//this is a the reduce function being supplied to the reduce call on the group runAvgGroup for add below
return function(elements, item) {
//get the position of the key value for this element in the sorted array and put it there
var pos = bisect.right(elements, keyfn(item));
elements.splice(pos, 0, item);
return elements;
};
}
function groupArrayRemove(keyfn) {
var bisect = d3.bisector(keyfn);//set the bisector value function
//elements is the group that we are reducing,item is the current item
//this is a the reduce function being supplied to the reduce call on the group runAvgGroup for remove below
return function(elements, item) {
//get the position of the key value for this element in the sorted array and splice it out
var pos = bisect.left(elements, keyfn(item));
if(keyfn(elements[pos])===keyfn(item))
elements.splice(pos, 1);
return elements;
};
}
function groupArrayInit() {
//for each key found by the key function return this array?
return []; //the result array for where the data is being inserted in sorted order?
}
I am not quite sure my perception of how this is working is quite right. Some of the magic isn't showing itself. Am I correct that elements is the group the reduce function is being called on ? also the array in groupArrayInit() how is it being indirectly populated?
Part of me feels that the functions supplied to the reduce call are really array.map functions not array.reduce functions but I just can't quite put my finger on why. having read the docs I am just not making a connection here.
Any help would be appreciated.
Also have I missed Pens/Fiddles that are created for all these examples? like this one
http://dc-js.github.io/dc.js/examples/complex-reduce.html which is where I started with this but had to download the csv and manually convert to Json.
--------------Update
I added some print statements to try to clarify how the add function is working
function groupArrayAdd(keyfn) {
var bisect = d3.bisector(keyfn); //set the bisector value function
//elements is the group that we are reducing,item is the current item
//this is a the reduce function being supplied to the reduce call on the group runAvgGroup for add below
return function(elements, item) {
console.log("---Start Elements and Item and keyfn(item)----")
console.log(elements) //elements grouped by run?
console.log(item) //not seeing the pattern on what this is on each run
console.log(keyfn(item))
console.log("---End----")
//get the position of the key value for this element in the sorted array and put it there
var pos = bisect.right(elements, keyfn(item));
elements.splice(pos, 0, item);
return elements;
};
}
and to print out the group's contents
console.log("RunAvgGroup")
console.log(runAvgGroup.top(Infinity))
which results in
Which appears to be incorrect b/c the values are not sorted by key (the run number)?
And looking at the results of the print statements doesn't seem to help either.
This looks basically right to me. The issues are just conceptual.
Crossfilter’s group.reduce is not exactly like either Array.reduce or Array.map. Group.reduce defines methods for handling adding new records to a group or removing records from a group. So it is conceptually similar to an incremental Array.reduce that supports an reversal operation. This allows filters to be applied and removed.
Group.top returns your list of groups. The value property of these groups should be the elements value that your reduce functions return. The key of the group is the value returned by your group accessor (defined in the dimension.group call that creates your group) or your dimension accessor if you didn’t define a group accessor. Reduce functions work only on the group values and do not have direct access to the group key.
So check those values in the group.top output and hopefully you’ll see the lists of elements you expect.

Converting a map into another map using the java 8 stream API

Say I have the following map:
Map<Member, List<Message>> messages = ... //constructed somehow
I would like to use the java 8 stream api in order to obtain a:
SortedMap<Message, Member> latestMessages = ...
Where the comparator passed into the SortedMap/TreeMap would be based on the message sendDate field.
Furthermore, of the list of sent messages, I would select the latest message which would become the key to the sorted map.
How can I achieve that?
edit 1:
Comparator<Message> bySendDate = Comparator.comparing(Message::getSendDate);
SortedMap<Message, Member> latestMessages = third.entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue().stream().max(bySendDate).get(), Map.Entry::getKey, (x, y) -> {
throw new AssertionError();
}, () -> new TreeMap(bySendDate.thenComparing(Comparator.comparing(Message::getId)))));
I get the following compilation error:
The method collect(Collector<? super T,A,R>) in the type Stream<T> is not applicable for the arguments (Collector<Map.Entry<Member,List<Message>>,?,TreeMap>)
Let’s dissolve this into two parts.
First, transform Map<Member, List<Message>> messages into a Map<Message, Member> latestMessages by reducing the messages for a particular communication partner (Member) to the latest:
Map<Message, Member> latestMessages0 = messages.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getValue().stream().max(Comparator.comparing(Message::getSendDate)).get(),
Map.Entry::getKey));
Here, the resulting map isn’t sorted but each mapping will contain the latest message shared with that participant.
Second, if you want to have the resulting map sorted by sendDate, you have to add another secondary sort criteria to avoid losing Messages which happen to have the same date. Assuming that you have a Long ID that is unique, adding this ID as secondary sort criteria for messages with the same date would be sufficient:
Comparator<Message> bySendDate=Comparator.comparing(Message::getSendDate);
SortedMap<Message, Member> latestMessages = messages.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getValue().stream().max(bySendDate).get(),
Map.Entry::getKey, (x,y) -> {throw new AssertionError();},
()->new TreeMap<>(bySendDate.thenComparing(Comparator.comparing(Message::getId)))));
Since sorting by the unique IDs should solve any ambiguity, I provided a merge function which will unconditionally throw, as calling it should never be required.

Mapping a given value to an action depending on certain characteristics

Suppose I have a certain value, and I want to do something with it depending on certain characteristics it might have.
For example, suppose the value is a string, and I want to print it to the screen if it starts with the letter L, save it to a file if it's length is less than 20 characters, and play a sound if the last character is the same as the first one.
One option of course is a simple if else if construct:
if (value[0] == 'L')
....
else if (value.Length < 20)
....
else if (value[0] == value.Last())
....
However with a lot of conditions, this can get ugly really fast. So the other option is a Dictionary. However I'm not sure how I can use a Dictionary to achieve this.
How can this be done?
You can construct a dictionary that contains conditions and actions that should be performed if a condition is met. In general, if you need to work with type T, this dictionary will have a type Dictionary<Predicate<T>, Action<T>>. For a string it can be:
var conditions = new Dictionary<Predicate<string>, Action<string>>
{
{s => s.StartsWith("L"), s => Console.WriteLine("Starts with L")},
{s => s.Length < 20, s => Console.WriteLine("Has fewer that 20 symbols")},
};
string input = "some input";
foreach (var condition in conditions)
{
if (condition.Key(input)) condition.Value(input);
}
In fact, you don't even need a Dictionary here - you can use List<Tuple<Predicate<string>, Action<string>>>, or, even better - to introduce a simple small class that contains a predicate and an action.

Why _.map iteratee's arguments is (value, key) not (key, value)?

According to the doc,
If list is a JavaScript object, iteratee's arguments will be (value, key, list).
I constantly have to check the doc to verify the order. Why is value, key not key, value?
[EDIT]
I guess I'm (always) confused because the for loop in CoffeeScript iterates on key, value:
yearsOld = max: 10, ida: 9, tim: 11
ages = for child, age of yearsOld
"#{child} is #{age}"
Because the value is the more important, and most generic part of mapping over structures. Maybe not so much with _.map over objects, but when you map over arrays you typically use a unary function (which takes only the value). The index (or key) is hardly ever used, so it became the second argument that is usually omitted from the parameter list.

Resources