mean, variance and std return a Float when the resulting shape is [1,1]
For example:
require 'nmatrix'
u = NMatrix.random([10, 1])
=>
[
[ 0.41627877754072795]
[ 0.9029664903552757]
[ 0.473493746395115]
[ 0.3283649411116434]
[ 0.5759227007695059]
[ 0.2367198471707127]
[ 0.7803035793094895]
[0.050427086280290245]
[ 0.13237866779390772]
[ 0.2539862299372726]
]
>> u.mean
=>
[
[0.4150842066663941] ]
>> u.variance
=>
[
[0.07543114515865457] ]
>> u.std
=>
[
[0.2746473104886603] ]
A coworker was writing a script with NMatrix today and was surprised with the current behavior. What do you folks think about returning the value itself (e.g. u.mean # => 0.4150842066663941) in this case?
I agree that this is confusing. I think we briefly discussed it in #92 where they were added.
I had originally argued that the way it is currently was better because otherwise, you either have to reduce the number of dimensions everywhere (in which case you can't slice assign the mean back to one of the rows, e.g., in a higher-dimensional matrix), or you have to have inconsistent behavior, where it only reduces dimension in this particular case you have here.
I think I'm inclined to do this after all and just special case this to return a bare number as you suggest. (I already implemented to_f, which will do this for a single-element matrix of any number of dimensions.) Go for it if there are no objections from anyone else!
Alright, I'll send a pull request today!
Just please make sure it works for 3+-dimensional tensors.
Hmmm, no, not yet. The problem (there are probably more) with this approach is that we'll introduce a lot of coupling in the definitions of #mean, #variance and #std.
What would solve this issue (but change the behavior of these methods altogether) is always flattening the matrix when applying these methods, except when dimen is specified. This is similar to what Numpy does, AFAIK.
Something like:
# A `flatten` method would be necessary, obviously.
def mean(dimen=nil)
reduce_dtype = :float64 if integer_dtype?
if dimen
# No flattening, same as before.
else
flattened = self.flatten
flattened.inject_rank(0, 0.0, reduce_dtype) do |mean, element|
mean + element
end / flattened.shape[0]
end
end
What do you think? Specifically, how would this change affect your use cases? It'd work for mine, but I'm only one of the users. :)
So long as the old behavior is still accessible, it's not a big deal to do it this way, I don't think. I could still do what I was doing before by changing .mean to .mean(0).
I personally find the auto-flattening behavior surprising, probably because I was previously more used to matlab than numpy.
matlab does:
a = reshape(0:8, 3, 3)
mean(a)
% => 1 4 7
numpy does:
a = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
a.mean()
# => 4
Either way works for me, though, so long as we document it!