web-dev-qa-db-ja.com

Rのmagritrrからの%>%のようなpythonのような機能パイプ

Rでは(magritrrのおかげで)%>%を介して、より機能的なパイピング構文で操作を実行できるようになりました。これは、これをコーディングする代わりに:

> as.Date("2014-01-01")
> as.character((sqrt(12)^2)

これを行うこともできます:

> "2014-01-01" %>% as.Date 
> 12 %>% sqrt %>% .^2 %>% as.character

私にはこれがより読みやすく、これはデータフレームを超えたユースケースに拡張されます。 python言語は同様のものをサポートしていますか?

60
cantdutchthis

これを行う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)
_
29
Dunes

パイプは 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を変更します。

24
shadowtalker

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
16
Sylvain Leroux

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'
16
smci

これを個人的なスクリプト用にしたいだけなら、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)
14
shadowtalker

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)
7
mhsekhavat

ビルド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'
_
6
yardsale8

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, ...)に書き換えるだけです。

https://pypi.org/project/pipeop/

https://github.com/robinhilliard/pipes

6
Robin Hilliard

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オブジェクトになります。

5
Eli Korvigo

代替ソリューションの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']
2
Legit Stack

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)
1