LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
_evalColumnExpression.py
Go to the documentation of this file.
1 # This file is part of pipe_tasks.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 
22 from __future__ import annotations
23 
24 __all__ = ("makeColumnExpressionAction", )
25 
26 import ast
27 import operator as op
28 
29 from typing import Mapping, MutableMapping, Set, Type, Union, Optional, Any, Iterable
30 
31 from numpy import log10 as log
32 from numpy import (cos, sin, cosh, sinh)
33 import pandas as pd
34 
35 from ..configurableActions import ConfigurableActionField
36 from ._baseDataFrameActions import DataFrameAction
37 
38 
39 OPERATORS = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
40  ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor,
41  ast.USub: op.neg}
42 
43 EXTRA_MATH = {"cos": cos, "sin": sin, "cosh": cosh, "sinh": sinh, "log": log}
44 
45 
46 class ExpressionParser(ast.NodeVisitor):
47  def __init__(self, **kwargs):
48  self.variablesvariables = kwargs
49  self.variablesvariables['log'] = log
50 
51  def visit_Name(self, node):
52  if node.id in self.variablesvariables:
53  return self.variablesvariables[node.id]
54  else:
55  return None
56 
57  def visit_Num(self, node):
58  return node.n
59 
60  def visit_NameConstant(self, node):
61  return node.value
62 
63  def visit_UnaryOp(self, node):
64  val = self.visit(node.operand)
65  return OPERATORS[type(node.op)](val)
66 
67  def visit_BinOp(self, node):
68  lhs = self.visit(node.left)
69  rhs = self.visit(node.right)
70  return OPERATORS[type(node.op)](lhs, rhs)
71 
72  def visit_Call(self, node):
73  if node.func.id in self.variablesvariables:
74  function = self.visit(node.func)
75  return function(self.visit(node.args[0]))
76  else:
77  raise ValueError("String not recognized")
78 
79  def generic_visit(self, node):
80  raise ValueError("String not recognized")
81 
82 
83 def makeColumnExpressionAction(className: str, expr: str,
84  exprDefaults: Optional[Mapping[str, Union[DataFrameAction,
85  Type[DataFrameAction]]]] = None,
86  docstring: str = None
87  ) -> Type[DataFrameAction]:
88  node = ast.parse(expr, mode='eval')
89 
90  # gather the specified names
91  names: Set[str] = set()
92  for elm in ast.walk(node):
93  if isinstance(elm, ast.Name):
94  names.add(elm.id)
95 
96  # remove the known Math names
97  names -= EXTRA_MATH.keys()
98 
99  fields: Mapping[str, ConfigurableActionField] = {}
100  for name in names:
101  if exprDefaults is not None and (value := exprDefaults.get(name)) is not None:
102  kwargs = {"default": value}
103  else:
104  kwargs = {}
105  fields[name] = ConfigurableActionField(doc=f"expression action {name}", **kwargs)
106 
107  # skip flake8 on N807 because this is a stand alone function, but it is
108  # intended to be patched in as a method on a dynamically generated class
109  def __call__(self, df: pd.DataFrame, **kwargs) -> pd.Series: # noqa: N807
110  values_map = {}
111  for name in fields:
112  values_map[name] = getattr(self, name)(df, **kwargs)
113 
114  parser = ExpressionParser(**values_map)
115  return parser.visit(node.body)
116 
117  # create the function to look up the columns for the dynamically created action
118  def columns(self) -> Iterable[str]:
119  for name in fields:
120  yield from getattr(self, name).columns
121 
122  dct: MutableMapping[str, Any] = {"__call__": __call__, "columns": property(columns)}
123  if docstring is not None:
124  dct['__doc__'] = docstring
125  dct.update(**fields)
126 
127  return type(className, (DataFrameAction, ), dct)
table::Key< int > type
Definition: Detector.cc:163
daf::base::PropertySet * set
Definition: fits.cc:912
Type[DataFrameAction] makeColumnExpressionAction(str className, str expr, Optional[Mapping[str, Union[DataFrameAction, Type[DataFrameAction]]]] exprDefaults=None, str docstring=None)