ENH: allow in-line expression assignment with df.eval
State: closed

This was a relatively easy extension of eval to allow in-line creation/assignment.

Allows one to basically use formulas to do things (pandas conquers excel!!!!)
- [x] docs
- [x] tests non-frame

```
In [11]: df = DataFrame(dict(a = range(5), b = range(5,10)))

In [12]: df
Out[12]: 
   a  b
0  0  5
1  1  6
2  2  7
3  3  8
4  4  9

In [13]: df.eval('c=a+b')

In [14]: df.eval('d=a+b+c')

In [15]: df.eval('a=1')

In [16]: df
Out[16]: 
   a  b   c   d
0  1  5   5  10
1  1  6   7  14
2  1  7   9  18
3  1  8  11  22
4  1  9  13  26
```

You can do this (this could maybe have a bit better syntax though)

```
In [31]: df = DataFrame(dict(a = range(5), b = range(5,10)))

In [32]: formulas = Series(['c=a+b','d=a*b'],index=['a','b'])

In [33]: df.apply(lambda x: df.eval(formulas[x.name]))
Out[33]: 
a    None
b    None
dtype: object

In [34]: df
Out[34]: 
   a  b   c   d
0  0  5   5   0
1  1  6   7   6
2  2  7   9  14
3  3  8  11  24
4  4  9  13  36
```


Labels:

Comments:
@cpcloud what do you think?

What happens with overlapping locals?

you mean like

```
a = 5
df.eval('a = b + c')
```

yep

I actually don't think its a problem because in this case the local doesn't matter

my question to use is, I am using `env.resolvers[0]` to figure out whom to do the assignment to, (its there when you call `df.eval`, but not when you call `pd.eval`). is that the 'right' way?

I think this works as expected (put tests in for this)

```
In [1]: df = DataFrame(np.random.randn(5, 2), columns=list('ab'))

In [2]: a = 1

In [3]: df.eval('a=1+b')

In [4]: df
Out[4]: 
          a         b
0 -0.641769 -1.641769
1  1.342992  0.342992
2  1.688316  0.688316
3  0.321509 -0.678491
4  1.652393  0.652393

In [5]: df.eval('a=a+b')

NameResolutionError: resolvers and locals overlap on names ['a']

In [6]: df.eval('a=@a+b')

In [7]: df
Out[7]: 
          a         b
0 -0.641769 -1.641769
1  1.342992  0.342992
2  1.688316  0.688316
3  0.321509 -0.678491
4  1.652393  0.652393
```

If you pass in `resolvers` explicitly (I actually think this should be disallowed and added if people want it), then `resolvers[0]` will not work in some cases. One thing that it sort of breaks is that variable look ups occur in the order of the resolvers, so shouldn't assignment be attempted until it either succeeds or exhausts all of the resolvers?

I wonder if an `assign(name, value)` method on `Scope` would be useful here?

I suppose `__setitem__` could be enlisted to do that.

FWIW I would like to eventually gut `Scope` and use `ChainMap` (in Python 3 `collections`) that is essentially `Scope`, but much less hacky and basically behaves like `dict` with a few bells a whistles to control the order of lookup. I'll do that for 0.14.

At some point I should also add `vars(pd.compat.builtin)` to locals so that builtin constants are there.

[`ChainMap`](http://docs.python.org/dev/library/collections#collections.ChainMap) is pretty cool.

ok...now use `visit_Assign` pretty easy, just had to figure out that `_visitor` actually has the data..

on the resolvers.

I think that I need to have an explicit way of having the target recorded by `df.eval`, maybe i'll just add a `target` ky to `Scope`?

@cpcloud all updated now....take a look when you have a chance, added a short note in the docs too.

otherwise look ok @cpcloud ?

yep looks okay