@@ -535,37 +535,93 @@ def _random(self, n, p, size=None):
535
535
# Set float type to float64 for numpy. This change is related to numpy issue #8317 (https://github.com/numpy/numpy/issues/8317)
536
536
p = p .astype ('float64' )
537
537
# Now, re-normalize all of the values in float64 precision. This is done inside the conditionals
538
- if size == p .shape :
539
- size = None
540
- elif size [- len (p .shape ):] == p .shape :
541
- size = size [:len (size ) - len (p .shape )]
542
538
543
- n_dim = n .squeeze ().ndim
544
-
545
- if (n_dim == 0 ) and (p .ndim == 1 ):
546
- p = p / p .sum ()
547
- randnum = np .random .multinomial (n , p .squeeze (), size = size )
548
- elif (n_dim == 0 ) and (p .ndim > 1 ):
549
- p = p / p .sum (axis = 1 , keepdims = True )
550
- randnum = np .asarray ([
551
- np .random .multinomial (n .squeeze (), pp , size = size )
552
- for pp in p
553
- ])
554
- randnum = np .moveaxis (randnum , 1 , 0 )
555
- elif (n_dim > 0 ) and (p .ndim == 1 ):
539
+ # np.random.multinomial needs `n` to be a scalar int and `p` a
540
+ # sequence
541
+ if p .ndim == 1 and (n .ndim == 0 or (n .ndim == 1 and n .shape [0 ] == 1 )):
542
+ # If `n` is already a scalar and `p` is a sequence, then just
543
+ # return np.multinomial with some size handling
556
544
p = p / p .sum ()
557
- randnum = np .asarray ([
558
- np .random .multinomial (nn , p .squeeze (), size = size )
559
- for nn in n
560
- ])
561
- randnum = np .moveaxis (randnum , 1 , 0 )
545
+ if size is not None :
546
+ if size == p .shape :
547
+ size = None
548
+ elif size [- len (p .shape ):] == p .shape :
549
+ size = size [:len (size ) - len (p .shape )]
550
+ randnum = np .random .multinomial (n , p , size = size )
551
+ return randnum .astype (original_dtype )
552
+ # The shapes of `p` and `n` must be broadcasted by hand depending on
553
+ # their ndim. We will assume that the last axis of the `p` array will
554
+ # be the sequence to feed into np.random.multinomial. The other axis
555
+ # will only have to be iterated over.
556
+ if n .ndim == p .ndim :
557
+ # p and n have the same ndim, so n.shape[-1] must be 1
558
+ if n .shape [- 1 ] != 1 :
559
+ raise ValueError ('If n and p have the same number of '
560
+ 'dimensions, the last axis of n must be '
561
+ 'have len 1. Got {} instead.\n '
562
+ 'n.shape = {}\n '
563
+ 'p.shape = {}.' .format (n .shape [- 1 ],
564
+ n .shape ,
565
+ p .shape ))
566
+ n_p_shape = np .broadcast (np .empty (p .shape [:- 1 ]),
567
+ np .empty (n .shape [:- 1 ])).shape
568
+ p = np .broadcast_to (p , n_p_shape + (p .shape [- 1 ],))
569
+ n = np .broadcast_to (n , n_p_shape + (1 ,))
570
+ elif n .ndim == p .ndim - 1 :
571
+ # n has the number of dimensions of p for the iteration, these must
572
+ # broadcast together
573
+ n_p_shape = np .broadcast (np .empty (p .shape [:- 1 ]),
574
+ n ).shape
575
+ p = np .broadcast_to (p , n_p_shape + (p .shape [- 1 ],))
576
+ n = np .broadcast_to (n , n_p_shape + (1 ,))
577
+ elif p .ndim == 1 :
578
+ # p only has the sequence array. We extend it with the dimensions
579
+ # of n
580
+ n_p_shape = n .shape
581
+ p = np .broadcast_to (p , n_p_shape + (p .shape [- 1 ],))
582
+ n = np .broadcast_to (n , n_p_shape + (1 ,))
583
+ elif n .ndim == 0 or (n .dim == 1 and n .shape [0 ] == 1 ):
584
+ # n is a scalar. We extend it with the dimensions of p
585
+ n_p_shape = p .shape [:- 1 ]
586
+ n = np .broadcast_to (n , n_p_shape + (1 ,))
587
+ else :
588
+ # There is no clear rule to broadcast p and n so we raise an error
589
+ raise ValueError ('Incompatible shapes of n and p.\n '
590
+ 'n.shape = {}\n '
591
+ 'p.shape = {}' .format (n .shape , p .shape ))
592
+
593
+ # Check what happens with size
594
+ if size is not None :
595
+ if size == p .shape :
596
+ size = None
597
+ _size = 1
598
+ elif size [- len (p .shape ):] == p .shape :
599
+ size = size [:len (size ) - len (p .shape )]
600
+ _size = np .prod (size )
601
+ else :
602
+ _size = np .prod (size )
603
+ else :
604
+ _size = 1
605
+
606
+ # We now flatten p and n up to the last dimension
607
+ p_shape = p .shape
608
+ p = np .reshape (p , (np .prod (n_p_shape ), - 1 ))
609
+ n = np .reshape (n , (np .prod (n_p_shape ), - 1 ))
610
+ # We renormalize p
611
+ p = p / p .sum (axis = 1 , keepdims = True )
612
+ # We iterate calls to np.random.multinomial
613
+ randnum = np .asarray ([
614
+ np .random .multinomial (nn , pp , size = _size )
615
+ for (nn , pp ) in zip (n , p )
616
+ ])
617
+ # We swap the iteration axis with the _size axis
618
+ randnum = np .moveaxis (randnum , 1 , 0 )
619
+ # We reshape the random numbers to the corresponding size + p_shape
620
+ if size is None :
621
+ randnum = np .reshape (randnum , p_shape )
562
622
else :
563
- p = p / p .sum (axis = 1 , keepdims = True )
564
- randnum = np .asarray ([
565
- np .random .multinomial (nn , pp , size = size )
566
- for (nn , pp ) in zip (n , p )
567
- ])
568
- randnum = np .moveaxis (randnum , 1 , 0 )
623
+ randnum = np .reshape (randnum , size + p_shape )
624
+ # We cast back to the original dtype
569
625
return randnum .astype (original_dtype )
570
626
571
627
def random (self , point = None , size = None ):
0 commit comments