
    M/PhNS                        d Z ddlmZ ddlmZ ddlZdMdZd Zd Zd	Z	 G d
 d          Z
edk    rdZdZdddgfdg dfgfZ	  e eg dg d                    Ze	r% e eg d ej        d                              Ze                    g g g d           g g g g dg dg dddgdZ e
eeee          Zd	ej        d<   dej        d<    ed            ed            ee                    ej                              ed             eej                    ed!            eej                   dd"d#d$gfd%d&d'd(gfd)g d*fgfd+d,gfgfZg d-d.gd/d.gd0d1gg g d2g d3d4gg d5g d6d.d7gg g d8g d9Z ed:  ed; ed<                    D                       Ze                    d=d>d?d@dAdBdC           	  e
eeee          Zd	ej        dD<   dej        dE<    edF            ed            ee                    ej                              ed             eej                    ed!            eej                    edG e e ej        !                                                                edH            eej"                    eej"                    edI            eej#                    edJ            eej$                   	  ej%        g dK          Z& ee'                    e&                      ej%        g dL          Z( ee'                    e(                     dS dS )Na  

Formulas
--------

This follows mostly Greene notation (in slides)
partially ignoring factors tau or mu for now, ADDED
(if all tau==1, then runmnl==clogit)

leaf k probability :

Prob(k|j) = exp(b_k * X_k / mu_j)/ sum_{i in L(j)} (exp(b_i * X_i / mu_j)

branch j probabilities :

Prob(j) = exp(b_j * X_j + mu*IV_j )/ sum_{i in NB(j)} (exp(b_i * X_i + mu_i*IV_i)

inclusive value of branch j :

IV_j = log( sum_{i in L(j)} (exp(b_i * X_i / mu_j) )

this is the log of the denominator of the leaf probabilities

L(j) : leaves at branch j, where k is child of j
NB(j) : set of j and it's siblings

Design
------

* splitting calculation transmission between returns and changes to
  instance.probs
  - probability for each leaf is in instance.probs
  - inclusive values and contribution of exog on branch level need to be
    added separately. handed up the tree through returns
* question: should params array be accessed directly through
  `self.recursionparams[self.parinddict[name]]` or should the dictionary
  return the values of the params, e.g. `self.params_node_dict[name]`.
  The second would be easier for fixing tau=1 for degenerate branches.
  The easiest might be to do the latter only for the taus and default to 1 if
  the key ('tau_'+branchname) is not found. I also need to exclude tau for
  degenerate branches from params, but then I cannot change them from the
  outside for testing and experimentation. (?)
* SAS manual describes restrictions on tau (though their model is a bit
  different), e.g. equal tau across sibling branches, fixed tau. The also
  allow linear and non-linear (? not sure) restriction on params, the
  regression coefficients. Related to previous issue, callback without access
  to the underlying array, where params_node_dict returns the actual params
  value would provide more flexibility to impose different kinds of restrictions.



bugs/problems
-------------

* singleton branches return zero to `top`, not a value
  I'm not sure what they are supposed to return, given the split between returns
  and instance.probs DONE
* Why does 'Air' (singleton branch) get probability exactly 0.5 ? DONE

TODO
----
* add tau, normalization for nested logit, currently tau is 1 (clogit)
  taus also needs to become part of params MOSTLY DONE
* add effect of branch level explanatory variables DONE
* write a generic multinomial logit that takes arbitrary probabilities, this
  would be the same for MNL, clogit and runmnl,
  delegate calculation of probabilities
* test on actual data,
  - tau=1 replicate clogit numbers,
  - transport example from Greene tests 1-level tree and degenerate sub-trees
  - test example for multi-level trees ???
* starting values: Greene mentiones that the starting values for the nested
  version come from the (non-nested) MNL version. SPSS uses constant equal
  (? check transformation) to sample frequencies and zeros for slope
  coefficient as starting values for (non-nested) MNL
* associated test statistics
  - (I do not think I will fight with the gradient or hessian of the log-like.)
  - basic MLE statistics can be generic
  - tests specific to the model (?)
* nice printouts since I'm currently collecting a lot of information in the tree
  recursion and everything has names

The only parts that are really necessary to get a functional nested logit are
adding the taus (DONE) and the MLE wrapper class. The rest are enhancements.

I added fake tau, one fixed tau for all branches. (OBSOLETE)
It's not clear where the tau for leaf should be added either at
original assignment of self.probs, or as part of the one-step-down
probability correction in the bottom branches. The second would be
cleaner (would make treatment of leaves and branches more symmetric,
but requires that initial assignment in the leaf only does
initialization. e.g self.probs = 1.  ???

DONE added taus

still todo:
- tau for degenerate branches are not identified, set to 1 for MLE
- rename parinddict to paramsinddict


Author: Josef Perktold
License : BSD (3-clause)
    )lrange)pprintN   c                     ddl m} t          j        |           t          j        |           z  }|                     |t          j        |                                                  |          }|S )a  generate integer random variables given probabilties

    useful because it can be used as index into any array or sequence type

    Parameters
    ----------
    w : 1d array_like
        sequence of weights, probabilities. The weights are normalized to add
        to one.
    size : int or tuple of ints
        shape of output array

    Returns
    -------
    rvs : array of shape given by size
        random variables each distributed according to the same discrete
        distribution defined by (normalized) w.

    Examples
    --------
    >>> np.random.seed(0)
    >>> randintw([0.4, 0.4, 0.2], size=(2,6))
    array([[1, 1, 1, 1, 1, 1],
           [1, 2, 2, 0, 1, 1]])

    >>> np.bincount(randintw([0.6, 0.4, 0.0], size=3000))/3000.
    array([ 0.59566667,  0.40433333])

    r   )random)numpy.randomr   npcumsumsumsearchsortedprodreshape)wsizer   prvss        n/var/www/html/test/jupyter/venv/lib/python3.11/site-packages/statsmodels/sandbox/regression/treewalkerclass.pyrandintwr   o   sf    > $#####
	!RVAYYA
....
/
/
7
7
=
=CJ    c                     t          | t                    r1| \  }}|g}|D ]$}|                    t          |                     %|S g S )z
    walk tree to get list of branches

    Parameters
    ----------
    tree : list of tuples
        tree as defined for RU2NMNL

    Returns
    -------
    branch : list
        list of all branch names

    )
isinstancetupleextendgetbranches)treenamesubtreeasts        r   r   r      sZ     $ gF 	& 	&BHH[__%%%%Ir   c                 6   t          | t                    r| \  }}|g}g }t          |          dk    r|g}ng }|D ]T}t          |          \  }}}	|                    |           |                    |           |                    |	           U|||fS g | gg fS )a  
    walk tree to get list of branches and list of leaves

    Parameters
    ----------
    tree : list of tuples
        tree as defined for RU2NMNL

    Returns
    -------
    branch : list
        list of all branch names
    leaves : list
        list of all leaves names

    r   )r   r   lengetnodesr   )
r   r   r   abaladegr   blds
             r   r"   r"      s    " $ gVw<<16DDD 	 	BrllGAq!IIaLLLIIaLLLKKNNNN2t|vr>r      c                   &    e Zd ZdZd Zd ZddZdS )RU2NMNLa  Nested Multinomial Logit with Random Utility 2 parameterization


    Parameters
    ----------
    endog : ndarray
        not used in this part
    exog : dict_like
        dictionary access to data where keys correspond to branch and leaf
        names. The values are the data arrays for the exog in that node.
    tree : nested tuples and lists
        each branch, tree or subtree, is defined by a tuple
        (branch_name, [subtree1, subtree2, ..., subtreek])
        Bottom branches have as subtrees the list of leaf names.
    paramsind : dictionary
        dictionary that maps branch and leaf names to the names of parameters,
        the coefficients for exogs)

    Methods
    -------
    get_probs

    Attributes
    ----------
    branches
    leaves
    paramsnames
    parinddict

    Notes
    -----
    endog needs to be encoded so it is consistent with self.leaves, which
    defines the columns for the probability array. The ordering in leaves is
    determined by the ordering of the tree.
    In the dummy encoding of endog, the columns of endog need to have the
    same order as self.leaves. In the integer encoding, the integer for a
    choice has to correspond to the index in self.leaves.
    (This could be made more robust, by handling the endog encoding internally
    by leaf names, if endog is defined as categorical variable with
    associated category level names.)

    c                 >    | _         | _        | _        | _        d _        i  _        i  _        i  _        i  _        i  _	        i  _
        t          |          \   _         _         _        t           j                   _        t#          d |                                D                       d  j        D             z    _        t           j                   _        d t+           j                  D              _         fd j                                        D              _        dt3          j        t           j                            z    _        t3          j        t           j                             _        d j         j         d <   d S )N c                     h | ]	}|D ]}|
S  r/   ).0jis      r   	<setcomp>z#RU2NMNL.__init__.<locals>.<setcomp>  s<     $3 $3 $3!01$3 $3+, %& $3 $3 $3 $3r   c                     g | ]}d |z  S )ztau_%sr/   )r0   bnames     r   
<listcomp>z$RU2NMNL.__init__.<locals>.<listcomp>  s    III%X-IIIr   c                     i | ]\  }}||	S r/   r/   )r0   idxr   s      r   
<dictcomp>z$RU2NMNL.__init__.<locals>.<dictcomp>  s*     ; ; ;
T$ ; ; ;r   c                 4    i | ]\  }}|fd |D             S )c                 *    g | ]}j         |         S r/   )	paramsidx)r0   r1   selfs     r   r6   z/RU2NMNL.__init__.<locals>.<dictcomp>.<listcomp>  s     <<<Qt~a0<<<r   r/   )r0   kvr=   s      r   r9   z$RU2NMNL.__init__.<locals>.<dictcomp>  sJ     B B B#&1Q <<<<!<<< B B Br         ?r   )endogdatadictr   	paramsind	branchsumprobsprobstxtbranchleavesbranchvalues
branchsumsbprobsr"   branchesleavesbranches_degenerater!   	nbranchessortedvaluesparamsnamesnparams	enumerater<   items
parinddictr	   arangerecursionparamszeros)r=   rA   exogr   rC   s   `    r   __init__zRU2NMNL.__init__   s   
	"
@H<t{D$<T]++ # $3 $3y/?/?/A/A $3 $3 $3 4 4II4=IIIJ 4+,,; ;'(899; ; ;B B B B*..*>*>*@*@B B B  "BIc$2B.C.C$D$DD!xD,<(=(=>>12dn_--...r   c                      | _                               j                   t          j         fd j        D                       }|S )a  
        obtain the probability array given an array of parameters

        This is the function that can be called by loglike or other methods
        that need the probabilities as function of the params.

        Parameters
        ----------
        params : 1d array, (nparams,)
            coefficients and tau that parameterize the model. The required
            length can be obtained by nparams. (and will depend on the number
            of degenerate leaves - not yet)

        Returns
        -------
        probs : ndarray, (nobs, nchoices)
            probabilities for all choices for each observation. The order
            is available by attribute leaves. See note in docstring of class



        c                 *    g | ]}j         |         S r/   )rE   )r0   leafr=   s     r   r6   z%RU2NMNL.get_probs.<locals>.<listcomp>?  s     IIIT
4 0IIIr   )rW   	calc_probr   r	   arrayrL   )r=   paramsprobs_arrays   `  r   	get_probszRU2NMNL.get_probs&  sM    .  &ty!!!hIIIIT[IIIJJr   Nc           	      
   | j         }| j        }| j        }| j        }t	          |t
                    r|\  }}g | j        |<   | j        | j        d|z                     }	t          r7t          d           t          |||         d|	           t          d|           g }
t          dk    rd}nt          dk    r	||         }n|}|D ]_}t          rt          |           |                     ||          }t          j        ||	z            }|
                    |           ||z   }`|
| j        |<   t          r/t          d           t          |           t          d	||           |rBt          rt          d
|           | j        |                             | j        |                    	 g | j        |<   t1          |          D ]l\  }}t          rt          dt3          |          |           t	          |t
                    s[| j        |                             | j        |                    | j        |         |z  | j        |<   t          rt          d|           |d         }t5          | j        |                   }t          j        ||z
                                            dk     sJ |
|         |z  }| j        |                             |           | j        |         D ]G}t          rt          d||||           | j        |         t          j        |d          z  | j        |<   Hnt          rt          d||           t          dk     r|S || j        |<   t          j        | j        |                   dk    r9t          j        | j        |         | j        | j        |                  z            }nd}|dk    s| j        | j        d|z                     }	nd}	||	|z  z   }||	t          j        |          z  z   S | j        | j        d|z                     }	t          rt          d
|           | j        |                             |           |dz   dd                    | j        |                   z  z   g| j        |<   t          j        t          j        | j        |         | j        | j        |                  z            |	z            }|| j        |<   t          dk    rt          j        |          S t          dk    rHt          j        ||                   }t          r%t          d|d                    |          |           |S t          dk    rd                    |          S dS )z5walking a tree bottom-up based on dictionary
        tau_z+----------- starting next branch-----------ztau=r   r)   r   r   z*----------- returning to branch-----------zbranchsum in branchparent*z-probz(%s)z, z"self.datadict[name], self.probs[k]zrepr(b)z&*********** branchsum at bottom branchg:0yE>
branchprobg-C6?zworking on branchtopzfinal branch withr-   N) rA   rB   rC   rD   r   r   rG   rW   r<   DEBUGprinttestxbr^   r	   expappendrH   r   rE   logrF   joinr   rJ   rS   reprr   absmaximumrI   rU   r_   )r=   r   re   rA   rB   rC   rD   r   r   taubranchvaluer&   bvtmpsumr>   ivbidxr5   
branchsum2bprobbranchxbleafprob	leavessums                          r   r^   zRU2NMNL.calc_probF  s    
=N	N	 dE"" G	% MD'&(Dd#&t~fTk'BCC *CDDDdHTNFC888i)))K{{		1$TN		 	 + + !HHH^^At,,VBsF^^""2&&&%N		&1Dd# >BCCCd+T9=== J ,(F+++!&)001B41HIIIF8 !#DK$W-- P Pa 4)T!WWd333!!e,, P K%,,TZ];;;$(JqMI$=DJqM SF	RRR aDE!$T%6t%<!=!=J6)j"899==??$FFFF'-i7EK%,,U333!.u5 P P  L!,q%KKK(,
1
5$8O8O(O
1P  <)4;;;axx  (1%74=.//11!vdmD&9"&"6tt7L"M'N  O  OHH  !HU{{.t~fTk/JKCCCi/#y(9(9"999 &t~fVm'DEC (h'''f%,,T222#''> &4>$3G)H)H H$I #JDM$ vbfT]4%8"&"6tt7L"M&N O O #$ % %H  (DJt {{vh'''1HXd^44	 O-tRWWT]]INNN  1wwt}}$ r   )N)__name__
__module____qualname____doc__rZ   rb   r^   r/   r   r   r+   r+      sY        ) )V&3 &3 &3T  @S% S% S% S% S% S%r   r+   __main__   rh   FlyAirGround)TrainCarBus)r   r   r   r   )Airdata	TraindataBusdataCardata   )rh   r   r   )GCTtmeConstAHinc)r   r   ConstT)r   r   ConstBr   r   )rh   r   r   r   r   r   r   z	Example 1z
---------
Treez
modru.probsB1r   r&   B2B21cr(   B22)efgB3hconstar   constbconst2x2)constcr   time)constdr   r   x22)conster   hince)constfr   hincfhincg)consthr   r   )r   r   r&   r   r   r   r(   r   r   r   r   r   r   rh   c                     g | ]}|S r/   r/   )r0   r2   s     r   r6   r6   4  s    ;;;Aa;;;r   abcdefgh   i  d            i,  )rh   r   r   r   r   r      z

Example 2zsum of probsrH   zbranch probabilitieszdegenerate branches)	        r@   r   r   r   r   r@   r@          @)r   r   r   r@   r   r   r   r   r   r   r   r   r   r   r   r   r@   r@   r@   r   r@   r@   )r   ))r   statsmodels.compat.pythonr   r   numpyr	   r   r   r"   rk   r+   r~   ri   rA   tree0dictziprB   rV   updaterC   modrurW   rj   r^   r   rE   tree2
paramsind2	datadict2modru2r   listrP   rH   rJ   rM   r_   params1rb   params2r/   r   r   <module>r      s  f fN - , , , , ,          " " " "H  .! ! !H 
I% I% I% I% I% I% I% I%Z zEE 3334E
 tCC666FFFH H I IH  '4:::!	!& & ' ' OOr!!# # $ $ $ ;;;333333 &> I GE8UI66E !E" E!	E+	E-	E%//%*
%
%&&&	E&MMM
F5:	E/
F5; Cy!Sz*/
 u E S/S/d
!
!
!
!
!
!G
"
"
"
"
"
"'
"


 J" ;;Zq		!:!:;;;<<IDsBRcRRSSS& WUIuj99F!"F2 !F1	E/	E-	E&

6;
'
'(((	E&MMM
F6;	E/
F6< 
E.##dd6<#6#6#8#899::;;;	E.	E&
	E%
	E
 !!!	E%,	E
   	E%
#$$$ bhDDDEEG	E%//'
"
"###bh O O O P PG	E&

7
#
#$$$$$] r   