Rでは(magritrr
のおかげで)%>%
を介して、より機能的なパイピング構文で操作を実行できるようになりました。これは、これをコーディングする代わりに:
> as.Date("2014-01-01")
> as.character((sqrt(12)^2)
これを行うこともできます:
> "2014-01-01" %>% as.Date
> 12 %>% sqrt %>% .^2 %>% as.character
私にはこれがより読みやすく、これはデータフレームを超えたユースケースに拡張されます。 python言語は同様のものをサポートしていますか?
これを行う1つの方法は、 macropy
というモジュールを使用することです。 Macropyを使用すると、作成したコードに変換を適用できます。したがって、_a | b
_はb(a)
に変換できます。これには多くの長所と短所があります。
Sylvain Lerouxが言及したソリューションと比較して、主な利点は、使用する関数のインフィックスオブジェクトを作成する必要がないことです。変換を使用するコードの領域をマークするだけです。第二に、変換はランタイムではなくコンパイル時に適用されるため、変換されたコードはランタイム中にオーバーヘッドを受けません。すべての作業は、ソースコードから最初にバイトコードが生成されるときに行われます。
主な欠点は、マクロピーが機能するために、マクロピーをアクティブにする特定の方法を必要とすることです(後述)。高速なランタイムとは対照的に、ソースコードの解析は計算が複雑であるため、プログラムの起動に時間がかかります。最後に、それは構文スタイルを追加します。これは、macropyに精通していないプログラマーがあなたのコードを理解するのを難しく感じるかもしれないことを意味します。
run.py
_import macropy.activate
# Activates macropy, modules using macropy cannot be imported before this statement
# in the program.
import target
# import the module using macropy
_
target.py
_from fpipe import macros, fpipe
from macropy.quick_lambda import macros, f
# The `from module import macros, ...` must be used for macropy to know which
# macros it should apply to your code.
# Here two macros have been imported `fpipe`, which does what you want
# and `f` which provides a quicker way to write lambdas.
from math import sqrt
# Using the fpipe macro in a single expression.
# The code between the square braces is interpreted as - str(sqrt(12))
print fpipe[12 | sqrt | str] # prints 3.46410161514
# using a decorator
# All code within the function is examined for `x | y` constructs.
x = 1 # global variable
@fpipe
def sum_range_then_square():
"expected value (1 + 2 + 3)**2 -> 36"
y = 4 # local variable
return range(x, y) | sum | f[_**2]
# `f[_**2]` is macropy syntax for -- `lambda x: x**2`, which would also work here
print sum_range_then_square() # prints 36
# using a with block.
# same as a decorator, but for limited blocks.
with fpipe:
print range(4) | sum # prints 6
print 'a b c' | f[_.split()] # prints ['a', 'b', 'c']
_
そして最後に、ハードワークを行うモジュール。あるプロセスから別のプロセスに出力を渡すシェル構文をエミュレートするため、機能パイプのfpipeと呼んでいます。
fpipe.py
_from macropy.core.macros import *
from macropy.core.quotes import macros, q, ast
macros = Macros()
@macros.decorator
@macros.block
@macros.expr
def fpipe(tree, **kw):
@Walker
def pipe_search(tree, stop, **kw):
"""Search code for bitwise or operators and transform `a | b` to `b(a)`."""
if isinstance(tree, BinOp) and isinstance(tree.op, BitOr):
operand = tree.left
function = tree.right
newtree = q[ast[function](ast[operand])]
return newtree
return pipe_search.recurse(tree)
_
パイプは Pandas 0.16.2 の新機能です。
例:
import pandas as pd
from sklearn.datasets import load_iris
x = load_iris()
x = pd.DataFrame(x.data, columns=x.feature_names)
def remove_units(df):
df.columns = pd.Index(map(lambda x: x.replace(" (cm)", ""), df.columns))
return df
def length_times_width(df):
df['sepal length*width'] = df['sepal length'] * df['sepal width']
df['petal length*width'] = df['petal length'] * df['petal width']
x.pipe(remove_units).pipe(length_times_width)
x
NB:PandasバージョンはPythonの参照セマンティクスを保持します。だからlength_times_width
は戻り値を必要としません。 x
を変更します。
python言語は同様のものをサポートしていますか?
「より機能的なパイピング構文」これは本当に「機能的な」構文ですか?代わりにRに「中置」構文を追加すると言います。
そうは言っても、 Pythonの文法 は、標準演算子を超えて中置表記法を直接サポートしていません。
そのようなものが本当に必要な場合は、独自の挿入表記法を実装するための出発点として Tomer Filibaのコード を使用する必要があります。
Tomer Filibaによるコードサンプルとコメント( http://tomerfiliba.com/blog/Infix-Operators/ ):
from functools import partial class Infix(object): def __init__(self, func): self.func = func def __or__(self, other): return self.func(other) def __ror__(self, other): return Infix(partial(self.func, other)) def __call__(self, v1, v2): return self.func(v1, v2)
この独特なクラスのインスタンスを使用して、関数を中置演算子として呼び出すための新しい「構文」を使用できるようになりました。
>>> @Infix ... def add(x, y): ... return x + y ... >>> 5 |add| 6
PyToolz[doc] は、任意の構成可能なパイプを許可しますが、パイプ演算子構文で定義されていないだけです。
クイックスタートについては、上記のリンクに従ってください。そして、ここにビデオチュートリアルがあります: http://pyvideo.org/video/2858/functional-programming-in-python-with-pytoolz
In [1]: from toolz import pipe
In [2]: from math import sqrt
In [3]: pipe(12, sqrt, str)
Out[3]: '3.4641016151377544'
これを個人的なスクリプト用にしたいだけなら、Pythonの代わりに Coconut を使用することを検討したいかもしれません。
CoconutはPythonのスーパーセットです。したがって、ココナッツの残りの部分を完全に無視しながら、ココナッツのパイプ演算子|>
を使用できます。
例えば:
def addone(x):
x + 1
3 |> addone
にコンパイルする
# lots of auto-generated header junk
# Compiled Coconut: -----------------------------------------------------------
def addone(x):
return x + 1
(addone)(3)
sspipe libraryを使用できます。 2つのオブジェクトp
およびpx
を公開します。 x %>% f(y,z)
と同様に、x | p(f, y, z)
とx %>% .^2
あなたは書ける x | px**2
。
from sspipe import p, px
from math import sqrt
12 | p(sqrt) | px ** 2 | p(str)
ビルドpipe
with Infix
Sylvain Leroux で示唆されているように、Infix
演算子を使用して中置pipe
を構築できます。これがどのように達成されるかを見てみましょう。
最初に、 Tomer Filiba のコードを示します
Tomer Filibaによるコードサンプルとコメント( http://tomerfiliba.com/blog/Infix-Operators/ ):
_from functools import partial class Infix(object): def __init__(self, func): self.func = func def __or__(self, other): return self.func(other) def __ror__(self, other): return Infix(partial(self.func, other)) def __call__(self, v1, v2): return self.func(v1, v2)
_この特異なクラスのインスタンスを使用して、関数を中置演算子として呼び出すための新しい「構文」を使用できます。
_>>> @Infix ... def add(x, y): ... return x + y ... >>> 5 |add| 6
_
パイプ演算子は、先行するオブジェクトを引数としてパイプに続くオブジェクトに渡すため、_x %>% f
_はf(x)
に変換できます。したがって、pipe
演算子は、次のようにInfix
を使用して定義できます。
_In [1]: @Infix
...: def pipe(x, f):
...: return f(x)
...:
...:
In [2]: from math import sqrt
In [3]: 12 |pipe| sqrt |pipe| str
Out[3]: '3.4641016151377544'
_
部分的な適用に関する注記
dpylr
の_%>%
_演算子は、関数の最初の引数を介して引数をプッシュするため、
_df %>%
filter(x >= 2) %>%
mutate(y = 2*x)
_
に対応
_df1 <- filter(df, x >= 2)
df2 <- mutate(df1, y = 2*x)
_
Python=)に似たものを実現する最も簡単な方法は、 currying を使用することです。toolz
ライブラリは、カリーを作成するcurry
デコレーター関数を提供します簡単に機能します。
_In [2]: from toolz import curry
In [3]: from datetime import datetime
In [4]: @curry
def asDate(format, date_string):
return datetime.strptime(date_string, format)
...:
...:
In [5]: "2014-01-01" |pipe| asDate("%Y-%m-%d")
Out[5]: datetime.datetime(2014, 1, 1, 0, 0)
_
_|pipe|
_が引数をlast argument positionにプッシュすることに注意してください。
_x |pipe| f(2)
_
に対応
_f(2, x)
_
カリー化された関数を設計する場合、静的引数(つまり、多くの例で使用される可能性のある引数)をパラメーターリストの前に配置する必要があります。
toolz
には、operator
モジュールのさまざまな関数を含む、多くの事前にカリー化された関数が含まれていることに注意してください。
_In [11]: from toolz.curried import map
In [12]: from toolz.curried.operator import add
In [13]: range(5) |pipe| map(add(2)) |pipe| list
Out[13]: [2, 3, 4, 5, 6]
_
これは、Rの以下にほぼ対応します。
_> library(dplyr)
> add2 <- function(x) {x + 2}
> 0:4 %>% sapply(add2)
[1] 2 3 4 5 6
_
他のインフィックス区切り文字の使用
他のPython演算子メソッド。たとえば、___or__
_と___ror__
_を___mod__
_と___rmod__
_は、_|
_演算子をmod
演算子に変更します。
_In [5]: 12 %pipe% sqrt %pipe% str
Out[5]: '3.4641016151377544'
_
Elixirの_|>
_パイプ演算子を逃したので、_>>
_ Python右シフト演算子を再解釈する単純な関数デコレーター(〜50行のコード)を作成しました。 astライブラリとcompile/execを使用したコンパイル時のElixirのようなパイプ:
_from pipeop import pipes
def add3(a, b, c):
return a + b + c
def times(a, b):
return a * b
@pipes
def calc()
print 1 >> add3(2, 3) >> times(4) # prints 24
_
a >> b(...)
をb(a, ...)
に書き換えるだけです。
2cを追加します。私は個人的にパッケージ fn を関数型プログラミングに使用しています。あなたの例は
_from fn import F, _
from math import sqrt
(F(sqrt) >> _**2 >> str)(12)
_
F
は、部分的なアプリケーションと合成のための機能スタイルの構文糖を持つラッパークラスです。 __
_は、匿名関数用のScalaスタイルのコンストラクターです(Pythonのlambda
と同様)。変数を表すため、1つの式で複数の__
_オブジェクトを組み合わせて、より多くの引数を持つ関数を取得できます(たとえば、__ + _
_は_lambda a, b: a + b
_と同等です)。 F(sqrt) >> _**2 >> str
は、必要な回数だけ使用できるCallable
オブジェクトになります。
代替ソリューションの1つは、ワークフローツールdaskを使用することです。構文的には面白くありませんが...
var
| do this
| then do that
...引き続き変数をチェーンに流し込むことができ、daskを使用すると、可能な場合は並列化の利点が得られます。
パイプチェーンパターンを実現するためにdaskを使用する方法は次のとおりです。
import dask
def a(foo):
return foo + 1
def b(foo):
return foo / 2
def c(foo,bar):
return foo + bar
# pattern = 'name_of_behavior': (method_to_call, variables_to_pass_in, variables_can_be_task_names)
workflow = {'a_task':(a,1),
'b_task':(b,'a_task',),
'c_task':(c,99,'b_task'),}
#dask.visualize(workflow) #visualization available.
dask.get(workflow,'c_task')
# returns 100
Elixirで作業した後、Pythonでパイピングパターンを使用したいと思いました。これはまったく同じパターンではありませんが、類似しており、前述したように、並列化の利点が追加されています。他のユーザーが最初に実行することに依存していないタスクをワークフロー内で取得するようにdaskに指示すると、それらは並行して実行されます。
より簡単な構文が必要な場合は、タスクの名前付けを処理するものでラップすることができます。もちろん、この状況では、パイプを最初の引数として使用するためにすべての関数が必要になり、視差の利点を失うことになります。しかし、それでよければ、次のようなことができます:
def dask_pipe(initial_var, functions_args):
'''
call the dask_pipe with an init_var, and a list of functions
workflow, last_task = dask_pipe(initial_var, {function_1:[], function_2:[arg1, arg2]})
workflow, last_task = dask_pipe(initial_var, [function_1, function_2])
dask.get(workflow, last_task)
'''
workflow = {}
if isinstance(functions_args, list):
for ix, function in enumerate(functions_args):
if ix == 0:
workflow['task_' + str(ix)] = (function, initial_var)
else:
workflow['task_' + str(ix)] = (function, 'task_' + str(ix - 1))
return workflow, 'task_' + str(ix)
Elif isinstance(functions_args, dict):
for ix, (function, args) in enumerate(functions_args.items()):
if ix == 0:
workflow['task_' + str(ix)] = (function, initial_var)
else:
workflow['task_' + str(ix)] = (function, 'task_' + str(ix - 1), *args )
return workflow, 'task_' + str(ix)
# piped functions
def foo(df):
return df[['a','b']]
def bar(df, s1, s2):
return df.columns.tolist() + [s1, s2]
def baz(df):
return df.columns.tolist()
# setup
import dask
import pandas as pd
df = pd.DataFrame({'a':[1,2,3],'b':[1,2,3],'c':[1,2,3]})
これで、このラッパーを使用して、これらの構文パターンのいずれかに従ってパイプを作成できます。
# wf, lt = dask_pipe(initial_var, [function_1, function_2])
# wf, lt = dask_pipe(initial_var, {function_1:[], function_2:[arg1, arg2]})
このような:
# test 1 - lists for functions only:
workflow, last_task = dask_pipe(df, [foo, baz])
print(dask.get(workflow, last_task)) # returns ['a','b']
# test 2 - dictionary for args:
workflow, last_task = dask_pipe(df, {foo:[], bar:['string1', 'string2']})
print(dask.get(workflow, last_task)) # returns ['a','b','string1','string2']
dfply
モジュールがあります。詳細については、次を参照してください。
https://github.com/kieferk/dfply
以下に例を示します。
from dfply import *
diamonds >> group_by('cut') >> row_slice(5)
diamonds >> distinct(X.color)
diamonds >> filter_by(X.cut == 'Ideal', X.color == 'E', X.table < 55, X.price < 500)
diamonds >> mutate(x_plus_y=X.x + X.y, y_div_z=(X.y / X.z)) >> select(columns_from('x')) >> head(3)