-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
min/max reductions are confusing #4235
Comments
Worth noting that we solve this problem in other cases by giving the reducing function a different name: |
An alternative view would be that rather |
I would be ok with a solution like that too. |
I think we should get this into 0.2 since it's a potentially breaking API change. In particular we must get rid of the awful |
Agreed that this is a noteworthy wart. |
I agree that the choice of using I am in favor of using |
We tried to fix this before by renaming the multi-arg versions of |
I suspect that renaming the reduction versions from |
@lindahua I think you are right. |
+1 for Dahua's names. |
I'm fine with that renaming. It solves the |
Here's an idea. The min(1,2) => 1
min([1, 2, 3], 10, [0]) => 0
min([1, 2, 3], [4, 5, 6]) => 1
min([1 2 3; 3 2 1], dim=1) => [1 2 1]
min([1 2 3; 3 2 1], dim=2) => [1 1]'
min([1 2 3; 3 2 1], [2 0 2] dim=1) => [1 0 1]
min([1 2 3; 3 2 1], [3 2 1; 1 2 3] dim=()) => [1 2 1; 1 2 1] I think this is equivalent to having |
Not a bad idea, but I'm worried about all that reducing. Would |
Ah, the dual nature of strings as both scalers and collections rears its homely head. I would vote to stick to doing this stuff for abstract arrays and leaving it at that. How often do you really want to reduce over the characters in a string? |
This seems to be what Max does in mathematica (flattens all of its arguments). It strikes me as a bit odd, particularly in the case of arrays of arrays. But I think I like it more than the elementwise behavior (which can be done with Honestly, at the end of the day I think all of these behaviors are non-obvious, and the one true max function is |
I don't really see why the version I proposed is so hard to reason about but I can see the value in having different names. How do you do an element-wise max in your scheme? Is that |
Absolutely not; as I said the one true max function is |
Your version is not so bad; it is basically the same as mine except that |
I really don't think that asking people to remember |
I don't consider it confusing to give different names to different operations. If True that elementwise max/min is a very useful operation, but your version doesn't provide it either. |
Sorry, I forgot you put the elementwise behavior into the |
One obstacle is that reducing multiple dimensions still gives an array:
|
Right. That's definitely is a bit of an issue. We could drop reduced dimensions if some of the arguments don't have them. |
FWIW, R does element-wise operations using
|
There do exist some ambiguities of the semantics when we talk about element-wise max. What is the desired behavior of julia> a = {[1, 3]};
julia> b = {[4, 2]};
julia> max(a, b)
1-element Array{Any,1}:
[4,2]
julia> map(max, a, b)
1-element Array{Any,1}:
[4,3] This kind of inconsistent behavior is partly ascribed to how we define max(x, y) = (x > y ? x : y) # -- (1)
max{T1<:Real,T2<:Real}(x::AbstractArray{T1}, y<:AbstractArray{T2}) # -- (2) When we call When we call I think that's why Jeff proposes to just keep However, I still think it is worth keeping element-wise
I really think that we should keep the element-wise version. However, we have to clean up the interface a little bit. For example, we should remove the In the mean time, the reduction functionality should be separated, and we may use |
The more I think about this, the more I feel the same with Jeff. Element-wise max/min does cause a lot of troubles when the elements themselves are arrays. There are possibly two ways out of this mess:
max(a, b) = (a > b ? a : b);
max(a::AbstractArray, b::AbstractArray) = error(" ... some useful error message ... ")
max{T1<:Real,T2<:Real}(a::AbstractArray{T1}, b::AbstractArray{T2}) = ... element wise max ...
max{T1<:Real,T2<:Real}(a::AbstractArray{T}, b::T2) = ...
max{T1<:Real,T2<:Real}(a::T1, b::AbstractArray{T2}) = ...
|
A third way to reduce the ambiguity is to stop allowing arrays to be comparable. I don't really see why we should make arrays (as a whole) to be comparable. I never see people compare arrays in this way as If we disable comparison between arrays, then there won't be ambiguities when we use a single function to express both meaning, since |
"Lexicographical comparison only makes sense for strings" This isn't quite true: there's a substantial literature on lexicographic preferences in psychology and economics. |
I should have clarified my point. Whereas lexicographical comparison between arrays do has its value in specific context, it is not a general operation as the comparison between real numbers. Therefore, it would be beneficial to encourage users to explicitly express their intent when lexicographical comparison is what they intend to do. This will not only make the code clearer and less ambiguous, but also avoid having other users caught in a big surprise when they see Using specific functions for lexicographical comparison in the place of |
Here is a detailed proposal that comes with code skeleton: Semantics:
With this semantics, major of codes that currently use Here is the codes to achieve this goal: # the emax function is an internal function that is supposed to be used
# within element-wise and reduction functions. It is not exposed.
emax{T}(a::T, b::T) = (a < b ? b : a)
emax{T<:Real}(a::T, b::T) = (a < b ? b : a)
emax(a::Float32, b::Float32) = ... # special implementation that takes care of NaN
emax(a::Float64, b::Float64) = ... # special implementation that takes care of NaN
emax{T1<:Real, T2<:Real}(a::T1, b::T2) = emax(promote(a, b)...)
emax(a::AbstractArray, b::AbstractArray) = error(" ... useful error message ... ")
emax(a::AbstractArray, b) = error(" ... ")
emax(a, b::AbstractArray) = error(" ... ")
# to support element-wise max when special ordering is supplied
emax(a, b, ord::Ordering) = lt(ord, a, b) ? b : a
## scalar max
# general case
max(a, b) = emax(a, b)
max(a, b, ord::Ordering) = emax(a, b, ord)
# element-wise for arrays
max(a::AbstractArray, b::AbstractArray) = ... # map(emax, a, b), implemented in an efficient way
max(a::AbstractArray, b) = ... # map(x -> emax(x, b), a) implemented in an efficient way
max(a, b::AbstractArray) = ... # map(x -> emax(a, x), b) implemented in an efficient way
# the following maps (x, y) --> emax(x, y, ord) in an element-wise manner in an efficient way
max(a::AbstractArray, b::AbstractArray, ord::Ordering) = ...
max(a::AbstractArray, b, ord::Ordering) = ...
max(a, b::AbstractArray, ord::Ordering) = ...
# reduction
function maximum(itr) # reduction also use emax internally
s = start(itr)
m = next(itr, s)
while !done(itr, s)
(v, s) = next(itr, s)
m = emax(m, v)
end
return m
end
maximum(itr, ord::Ordering) = ... # use ord for reduction
maximum(a::AbstractArray, dims::Dims) = ... # reduction along dimensions
maximum(a::AbstractArray, dims::Dims, ord::Ordering) = ... # reduction along dimensions using ord
# deprecate max(a) for single argument case in favor of maximum(a)
# deprecate max(a, (), dims) in favor of maximum(a, dims) I think this properly address all the issues. The introduction of Also, this would not introduce major breakage. The use of |
I haven't caught up on this whole conversation, but I would note that while function ord(lt::Function, by::Function, rev::Bool, order::Ordering=Forward)
o = (lt===isless) & (by===identity) ? order :
(lt===isless) & (by!==identity) ? By(by) :
(lt!==isless) & (by===identity) ? Lt(lt) :
Lt((x,y)->lt(by(x),by(y)))
rev ? ReverseOrdering(o) : o
end This isn't the most elegant thing in the world, but it's really just a stop-gap until comparison functions can be specialized on and inlined. It keeps the same interface that you'd want if that were the case while allowing specialization. I was actually considering removing the |
@lindahua 's latest proposal looks pretty good. After some thought, I agree that making It's a good idea to make One small change I'd propose is to implement
so that |
@JeffBezanson: Yes, your modification makes sense. |
This plan seems good to me although I'm a little concerned that the fancy NaN logic and other Aside: I figured out a reasonable way to deprecate the |
Shame; in this particular case |
Well, I could always undeprecate it. |
Next we have to decide how lexicographic ordering works. Maybe:
|
This may be taking it too far, but lexicographic ordering should maybe be parameterized by another ordering that's used for comparing elements. |
Dahua's proposal with Jeff's modifications seems like a huge step forward. I think Stefan's idea of having lexicographic ordering depend on another ordering seems sound to me. We might also want to allow |
I don't understand the generalized thing. Can you elaborate? |
Here's a brief introduction, where it explains how you can generalize component-wise inequality: http://www.nt.tuwien.ac.at/uploads/media/lecture7.pdf Might be too obscure. Just one of the other useful orderings I know of for vectors after moving beyond lexicographic orderings. |
I think the easiest plan may be implement a subtype of The Generalized order mentioned by @johnmyleswhite is mainly used in convex optimization context and has to be defined with respect to a vector space. These things can be implemented in a package. |
I will make updates to NumericExtensions, Stats, Distributions, and other downstream packages as soon as these changes are merged. |
make minimum and maximum use scalarmin and scalarmax, which are min and max but exclude arrays remove isless for AbstractArray add LexicographicOrdering and use it in sortrows and sortcols currently LexicographicOrdering is implemented with cmp(), which does the right thing for arrays, but we might not want to keep it that way
Just caught up with this thread. It would be great to do a blog post summarizing the design when all the changes are in. |
Hi @rosangelaoliveira4, welcome to Julia. Please refer to the manual, and ask questions on StackOverflow or the julia-users mailing list. Thanks. |
This is not the right place to ask basic usage questions. |
The generic
max(itr)
callsmax
on pairs of values, whilemax(Array)
calls>
on pairs of values. This is a problem becausemax(A::Array,B::Array)
is elementwise, while all other definitions ofmax(A,B)
return eitherA
orB
depending which is "larger".At the very least, all 1-argument
max
definitions should be consistent on whether they doreduce(max, ...)
or call>
.Then we are just left with the elementwise behavior, which may be a fight for another day.
The text was updated successfully, but these errors were encountered: