@@ -325,14 +325,14 @@ def test__ColumnBuilder():
325325 cb_intercept .build ({f1 : [1 , 2 , 3 ], f2 : [1 , 2 , 3 ], f3 : [1 , 2 , 3 ]}, mat3 )
326326 assert np .allclose (mat3 , 1 )
327327
328- def _factors_memorize (factors , data_iter_maker ):
328+ def _factors_memorize (factors , data_iter_maker , eval_env ):
329329 # First, start off the memorization process by setting up each factor's
330330 # state and finding out how many passes it will need:
331331 factor_states = {}
332332 passes_needed = {}
333333 for factor in factors :
334334 state = {}
335- which_pass = factor .memorize_passes_needed (state )
335+ which_pass = factor .memorize_passes_needed (state , eval_env )
336336 factor_states [factor ] = state
337337 passes_needed [factor ] = which_pass
338338 # Now, cycle through the data until all the factors have finished
@@ -362,7 +362,7 @@ def __init__(self, requested_passes, token):
362362 self ._chunk_in_pass = 0
363363 self ._seen_passes = 0
364364
365- def memorize_passes_needed (self , state ):
365+ def memorize_passes_needed (self , state , eval_env ):
366366 state ["calls" ] = []
367367 state ["token" ] = self ._token
368368 return self ._requested_passes
@@ -389,7 +389,7 @@ def __call__(self):
389389 f1 = MockFactor (1 , "f1" )
390390 f2a = MockFactor (2 , "f2a" )
391391 f2b = MockFactor (2 , "f2b" )
392- factor_states = _factors_memorize (set ([f0 , f1 , f2a , f2b ]), data )
392+ factor_states = _factors_memorize (set ([f0 , f1 , f2a , f2b ]), data , {} )
393393 assert data .calls == 2
394394 mem_chunks0 = [("memorize_chunk" , 0 )] * data .CHUNKS
395395 mem_chunks1 = [("memorize_chunk" , 1 )] * data .CHUNKS
@@ -615,7 +615,7 @@ def _make_term_column_builders(terms,
615615 term_to_column_builders [term ] = column_builders
616616 return new_term_order , term_to_column_builders
617617
618- def design_matrix_builders (termlists , data_iter_maker , NA_action = "drop" ):
618+ def design_matrix_builders (termlists , data_iter_maker , eval_env , NA_action = "drop" ):
619619 """Construct several :class:`DesignMatrixBuilders` from termlists.
620620
621621 This is one of Patsy's fundamental functions. This function and
@@ -629,6 +629,14 @@ def design_matrix_builders(termlists, data_iter_maker, NA_action="drop"):
629629 simple iterator because sufficiently complex formulas may require
630630 multiple passes over the data (e.g. if there are nested stateful
631631 transforms).
632+ :arg eval_env: Either a :class:`EvalEnvironment` which will be used to
633+ look up any variables referenced in `termlists` that cannot be
634+ found in `data_iter_maker`, or else a depth represented as an
635+ integer which will be passed to :meth:`EvalEnvironment.capture`.
636+ ``eval_env=0`` means to use the context of the function calling
637+ :func:`design_matrix_builders` for lookups. If calling this function
638+ from a library, you probably want ``eval_env=1``, which means that
639+ variables should be resolved in *your* caller's namespace.
632640 :arg NA_action: An :class:`NAAction` object or string, used to determine
633641 what values count as 'missing' for purposes of determining the levels of
634642 categorical factors.
@@ -643,14 +651,25 @@ def design_matrix_builders(termlists, data_iter_maker, NA_action="drop"):
643651
644652 .. versionadded:: 0.2.0
645653 The ``NA_action`` argument.
654+ .. versionadded:: 0.4.0
655+ The ``eval_env`` argument.
646656 """
657+ # Check type of eval_env to help people migrating to 0.4.0. Third
658+ # argument used to be NA_action (a string). Having the check for
659+ # eval_env's type gives people migrating to 0.4.0 who used NA_action
660+ # not as a keyword argument a nice error message here, instead of a
661+ # more obscure backtrace later on.
662+ if not isinstance (eval_env , six .integer_types + (EvalEnvironment ,)):
663+ raise TypeError ("Parameter 'eval_env' must be either an integer or an instance "
664+ "of patsy.EvalEnvironment." )
665+ eval_env = EvalEnvironment .capture (eval_env , reference = 1 )
647666 if isinstance (NA_action , str ):
648667 NA_action = NAAction (NA_action )
649668 all_factors = set ()
650669 for termlist in termlists :
651670 for term in termlist :
652671 all_factors .update (term .factors )
653- factor_states = _factors_memorize (all_factors , data_iter_maker )
672+ factor_states = _factors_memorize (all_factors , data_iter_maker , eval_env )
654673 # Now all the factors have working eval methods, so we can evaluate them
655674 # on some data to find out what type of data they return.
656675 (num_column_counts ,
0 commit comments