ENH: Support array-like n in random.random.multinomial#9710
ENH: Support array-like n in random.random.multinomial#9710ColCarroll wants to merge 3 commits intonumpy:masterfrom
n in random.random.multinomial#9710Conversation
| Parameters | ||
| ---------- | ||
| n : int | ||
| n : int or array_like of ints |
There was a problem hiding this comment.
Needs .. versionchanged:: 1.14.0 with a note that allowing array_like here is new. You can find examples in the file.
numpy/random/mtrand/mtrand.pyx
Outdated
| mnix[i+d-1] = dn | ||
|
|
||
| i = i + d | ||
| for k from 0 <= k < m: |
There was a problem hiding this comment.
This is outdated syntax for Pyrex compatibility
|
I'm wary of adding another Can we make it work as: I'm also wondering if accepting an ND |
|
I removed what I think was the pyrex compatibility, but I'm not sure I understand your point about broadcasting? Would you guess the right approach is (in case |
|
I tried a bit to use |
This maybe feels like a feature? Force the user to make the integers Essentially, there are two different ways we could go with broadcasting. Here I'm using
Version 2 is more complex, but it does preserve the meanings of dimensions, which is often handy |
|
|
|
Looking at the |
|
Actually this is totally possible, there's just a quirk in the python-side interface to nditer ( #9808) that makes it non-obvious. Here's what I believe is a python implementation of what I'm describing. Caveat: I have no idea what I'm doing with import numpy as np
from numpy.lib.stride_tricks import as_strided
from numpy.random import multinomial as m_old
def multinomial(n, p):
it = np.nditer(
(n, p, None),
flags=[
"zerosize_ok", 'multi_index', 'reduce_ok' # no idea if these are needed
],
op_flags=[
['readonly'],
['readonly'],
['writeonly', 'allocate']
]
)
n, p, out = it.operands
p_shape = np.array(p.shape)
n_shape = np.array(p.shape)
out_shape = np.array(out.shape)
# axes containing probabilities, counted from the end
p_axs = p_shape != 1 # todo: let this be passed in
p_axs, = p_axs.nonzero()
p_axs -= p.ndim
if not np.all(n_shape[p_axs] == 1):
raise ValueError("Axes of n must be length 1 where probabilities are not")
# don't iterate over these
for ax_i in p_axs:
it.remove_axis(it.ndim + ax_i)
it.remove_multi_index() # I think this just makes things faster?
# it.enable_external_loop() # TODO: maybe a speedup to be had by using this
# convert to arrays so we can index these
p_strides = np.array(p.strides)
out_strides = np.array(out.strides)
while not it.finished:
ni, pi, outi = it.value
# workaround for gh-9808
pi = as_strided(pi, shape=p_shape[p_axs], strides=p_strides[p_axs])
outi = as_strided(outi, shape=out_shape[p_axs], strides=out_strides[p_axs])
outi[...] = m_old(ni, pi.ravel()).reshape(p.shape)
it.iternext()
return out |
|
Digging this up and asking if there is anything I could do to help this along? |
Add broadcasting to multinomial xref numpy/numpy#9710
|
Closing, |
This changes the
numpy.random.multinomialAPI to be more similar to the API fornumpy.random.binomial. In particular, something like this:I did some light benchmarking, and it seems like the current use case is equally fast, while this provides at least some speed up over
np.array([np.random.multinomial(n, pvals) for n in nvals]).