Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@aes macro update to support calculated aesthetics #71

Open
rdboyes opened this issue Apr 7, 2024 · 15 comments
Open

@aes macro update to support calculated aesthetics #71

rdboyes opened this issue Apr 7, 2024 · 15 comments
Assignees
Labels
enhancement New feature or request

Comments

@rdboyes
Copy link
Member

rdboyes commented Apr 7, 2024

0.6.3 added support for calculated aesthetics in the function aes - need to extend this feature to the macro @aes.

@rdboyes rdboyes added the enhancement New feature or request label Apr 7, 2024
@kdpsingh kdpsingh self-assigned this Apr 8, 2024
@adknudson
Copy link
Member

Can you give an example of how to call aes with a calculated column? I'm trying to make sense of the function code but having trouble.

Is the goal to have something like @aes(x = sqrt(variable))?

@rdboyes
Copy link
Member Author

rdboyes commented May 3, 2024

Yes - e.g. this example from the R ggplot docs:
image

so the ideal would be to have operators and functions work, like:

ggplot(penguins) + geom_point(@aes(x = bill_length_mm / 10, y = sqrt(bill_depth_mm)) 

@kdpsingh
Copy link
Member

kdpsingh commented May 3, 2024

Sorry I have been so slow on this one. If you'd like to work on it, I can outline what I think is the best way to do it in a reply to this message. Otherwise, I'm hoping to get to it soon-ish.

@rdboyes
Copy link
Member Author

rdboyes commented May 3, 2024

The output needs to be in the form of a "column transformation" - the way I've set up the draw code to run the calculations, that looks like an entry in a key => pair Dict with the following parts:

  • the key is the aes that is being calculated, as a symbol (e.g. :x)
  • the first part of the pair is a Vector{Symbol} with the input aes, (e.g. [:x, :y])
  • the second part of the pair is the function to apply. It should take the args (target, source, data) and return a Dict{Symbol, PlottableData} - e.g. this sort function:
function sort_by_fn(target::Symbol, source::Vector{Symbol}, data::DataFrame)
    perm = sortperm(data[!, source[2]])

    return Dict{Symbol, PlottableData}(
        target => PlottableData(
            data[perm, source[1]],
            identity,
            nothing,
            nothing
        )
    )      
end

sort_by = AesTransform(sort_by_fn)

@rdboyes
Copy link
Member Author

rdboyes commented May 3, 2024

I know the complexity is weirdly high here - and I'm open to any rewrite suggestions. This was the simplest structure I could come up with that was also flexible enough to encode all of the possible aes requirements (look for example at geom_contour)

@kdpsingh
Copy link
Member

kdpsingh commented May 3, 2024

Does your code currently call TidierData?

My suggestion would be to pass the transformation through TidierData.@mutate. That way, @aes would support autovectorization, interpolation, and all the other goodies.

@rdboyes
Copy link
Member Author

rdboyes commented May 3, 2024

It does - what would mutate return, though? Would it be DataFrames syntax? We still have to store that and run it later, since when the aes is called, we don't necessarily have access to the DataFrame that we're referencing

@kdpsingh
Copy link
Member

kdpsingh commented May 3, 2024

That was going to be my question. Isn't the underlying data part of the ggplot struct?

If it was, I was thinking we could create a temporary column containing the mutated result and then reference that column.

@rdboyes
Copy link
Member Author

rdboyes commented May 3, 2024

That was how I was originally doing it, but some of the Makie plots require inputs that can't be stored as a DataFrame column (e.g. geom_contour requires a Matrix as input)

@rdboyes
Copy link
Member Author

rdboyes commented May 3, 2024

Is there an existing way to have @mutate return the code but not run it?

@kdpsingh
Copy link
Member

kdpsingh commented May 3, 2024

Would wrapping it inside of an anonymous function work?

For example, df -> @mutate(df, TidierPlots_x = mpg / 2)? Here, df represents the data frame and not a column per se.

I'm still trying to wrap my head around why we couldn't modify the struct in place rather than defer the evaluation. I haven't tried it, so you definitely have a better understanding.

@rdboyes
Copy link
Member Author

rdboyes commented May 3, 2024

It's because in call like this:

ggplot(penguins) + 
  geom_point(@aes(bill_length_mm/10, bill_depth_mm))

geom_point(@aes(bill_length_mm/10, bill_depth_mm)) will evaluate to a Geom object with no data in it - it will only inherit the data later when you go to draw it. So there's nothing for the mutate function to immediately act on

But wrapping it in an anonymous function could work, yes

@kdpsingh
Copy link
Member

kdpsingh commented May 3, 2024

Got it. Another option would be to wrap it inside of an expression. We would interpolate the expression into @mutate when ready to draw the plot.

For example, mpg / 2 would become :(mpg / 2).

@rdboyes
Copy link
Member Author

rdboyes commented Jun 27, 2024

I've made progress on this - I was removing some of the "deep type piracy" and made it easier to implement as a consequence. Its almost working, but there's a questionable eval call that I think is causing some troubles - take a look here if you get a chance:

aes_dict[String(aes_ex.args[1])] = tidy.args[2] => eval(tidy.args[3])[1]

@kdpsingh
Copy link
Member

Thanks! I will take a look at this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants