UFO ET IT

R의 magritrr에서 %> %와 같은 파이썬의 기능성 파이프

ufoet 2020. 12. 3. 21:07
반응형

R의 magritrr에서 %> %와 같은 파이썬의 기능성 파이프


R (덕분에 magritrr)에서를 통해 더 기능적인 파이핑 구문으로 작업을 수행 할 수 있습니다 %>%. 이것은 이것을 코딩하는 대신에 의미합니다 :

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

다음과 같이 할 수도 있습니다.

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

나에게 이것은 더 읽기 쉽고 이것은 데이터 프레임을 넘어서는 사용 사례로 확장됩니다. 파이썬 언어가 비슷한 것을 지원합니까?


이를 수행하는 한 가지 가능한 방법은라는 모듈을 사용하는 것 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제자리에서 수정 됩니다.


파이썬 언어가 비슷한 것을 지원합니까?

"더 많은 기능적인 파이핑 구문" 이 실제로 더 "기능적인"구문입니까? 대신 R에 "infix"구문을 추가합니다.

즉, 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의 상위 집합입니다. 따라서 Coconut의 파이프 연산자를 사용할 수 |>있으며 나머지 Coconut 언어는 완전히 무시할 수 있습니다.

예를 들면 :

def addone(x):
    x + 1

3 |> addone

컴파일

# lots of auto-generated header junk

# Compiled Coconut: -----------------------------------------------------------

def addone(x):
    return x + 1

(addone)(3)

sspipe 라이브러리 를 사용할 수 있습니다 . 두 개의 객체 ppx. 유사하게 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)

건축 pipeInfix

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)

파이썬에서 비슷한 것을 얻는 가장 쉬운 방법은 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|에 인수를 밀어 마지막 인수의 위치 , 즉

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 연산자 메서드를 재정 의하여 Infix 호출을 둘러싼 기호를 변경할 수 있습니다. 예를 들어, 스위칭 __or____ror____mod____rmod__변화한다 |받는 오퍼레이터 mod오퍼레이터.

In [5]: 12 %pipe% sqrt %pipe% str
Out[5]: '3.4641016151377544'

|>Elixir 파이프 연산자를 놓 쳤기 때문에 >>ast 라이브러리와 컴파일 / 실행을 사용하여 컴파일 타임에 Python 오른쪽 시프트 연산자를 매우 Elixir와 유사한 파이프로 재 해석하는 간단한 함수 데코레이터 (~ 50 줄의 코드)를 만들었습니다 .

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


내 2c 추가. 저는 개인적 으로 기능적 스타일 프로그래밍을 위해 패키지 fn사용합니다 . 귀하의 예는

from fn import F, _
from math import sqrt

(F(sqrt) >> _**2 >> str)(12)

F부분 적용 및 구성을위한 기능적 스타일의 구문 설탕이있는 래퍼 클래스입니다. _익명 함수를위한 Scala 스타일 생성자입니다 (Python의 lambda) 와 유사합니다 . 변수를 나타내므로 _하나의 표현식에서 여러 객체를 결합 하여 더 많은 인수가있는 함수를 얻을 수 있습니다 (예 : _ + _lambda a, b: a + b). 원하는만큼 사용할 수 F(sqrt) >> _**2 >> str있는 Callable개체가 생성됩니다.


한 가지 대안은 워크 플로 도구 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

After having worked with elixir I wanted to use the piping pattern in Python. This isn't exactly the same pattern, but it's similar and like I said, comes with added benefits of parallelization; if you tell dask to get a task in your workflow which isn't dependant upon others to run first, they'll run in parallel.

If you wanted easier syntax you could wrap it in something that would take care of the naming of the tasks for you. Of course in this situation you'd need all functions to take the pipe as the first argument, and you'd lose any benefit of parallization. But if you're ok with that you could do something like this:

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]})

Now, with this wrapper, you can make a pipe following either of these syntactical patterns:

# wf, lt = dask_pipe(initial_var, [function_1, function_2])
# wf, lt = dask_pipe(initial_var, {function_1:[], function_2:[arg1, arg2]})

like this:

# 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']

There is dfply module. You can find more information at

https://github.com/kieferk/dfply

Some examples are:

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)

참고URL : https://stackoverflow.com/questions/28252585/functional-pipes-in-python-like-from-rs-magritrr

반응형