programing

R's magrittr의 python에서 %>%와 같은 기능 파이프

topblog 2023. 10. 25. 21:58
반응형

R's magrittr의 python에서 %>%와 같은 기능 파이프

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

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

다음을 수행할 수도 있습니다.

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

저는 이것이 더 읽기 쉽고 데이터 프레임 이상의 사용 사례로 확장된다고 생각합니다.파이썬 언어도 비슷한 것을 지원합니까?

파이프는 판다 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제자리에

이것을 하는 한 가지 가능한 방법은 .Macropy라는 모듈을 사용하는 것입니다. Macrophy는 당신이 작성한 코드에 변환을 적용할 수 있게 해줍니다.따라서a | b로 변형될 수 있습니다.b(a) 있습니다 이것은 많은 장점과 단점을 가지고 있습니다.

실뱅 르루(Sylvain Leroux)가 언급한 해결책과 비교하여, 주요 장점은 사용하고자 하는 함수에 대한 infix 개체를 생성할 필요가 없다는 것입니다. 변환을 사용하려는 코드 영역을 표시하기만 하면 됩니다.둘째, 변환은 런타임이 아닌 컴파일 타임에 적용되므로 변환된 코드는 런타임 동안 오버헤드가 발생하지 않습니다. 모든 작업은 소스 코드에서 바이트 코드를 처음 생성할 때 수행됩니다.

주요 단점은 매크로피가 작동하려면 특정 방법을 활성화해야 한다는 것입니다(뒤에 언급됨).더 빠른 런타임과 대조적으로 소스 코드의 구문 분석은 계산적으로 더 복잡하므로 프로그램을 시작하는 데 더 오랜 시간이 걸릴 것입니다.마지막으로, 그것은 매크로피에 익숙하지 않은 프로그래머들이 당신의 코드를 이해하기 더 어려워할 수도 있다는 것을 의미하는 구문 스타일을 추가합니다.

예제 코드:

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

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'

개인적인 스크립팅을 위해 이것을 원한다면 파이썬 대신 코코넛을 사용하는 것을 고려해 볼 수 있습니다.

코코넛은 파이썬의 슈퍼세트입니다.따라서 코코넛의 파이프 연산자를 사용할 수 있습니다.|>, 코코넛 언어를 완전히 무시하면서 말이죠

예를 들어,

def addone(x):
    x + 1

3 |> addone

에 모읍니다.

# lots of auto-generated header junk

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

def addone(x):
    return x + 1

(addone)(3)

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

"더 기능적인 배관 구문" 이것이 정말로 더 "기능적인" 구문입니까?대신 R에 "infix" 구문을 추가한다고 말하고 싶습니다.

그렇다고 해도 파이썬의 문법은 표준 연산자 이상의 인픽스 표기법을 직접 지원하지는 않습니다.


그런 것이 정말 필요하다면, 토머 필리바의 코드를 시작점으로 삼아 독자적인 infix 표기법을 구현해야 합니다.

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 연산자로서 호출 기능에 새로운 "syntax"를 사용할 수 있습니다.

>>> @Infix
... def add(x, y):
...     return x + y
...
>>> 5 |add| 6

스파이프 라이브러리를 이용할 수 있습니다.두 개의 물체가 노출됩니다.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)

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

놓쳤어요.|>Elixir의 파이프 오퍼레이터 그래서 나는 그것을 재해석하는 심플한 기능 데코레이터(~50줄의 코드)를 만들었습니다.>>ast 라이브러리를 사용하여 컴파일할 때 매우 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

파이프 기능을 구현하기 위해 서드파티 라이브러리나 혼란스러운 연산자 트릭이 필요 없습니다. 기본 사항은 본인이 직접 쉽게 수행할 수 있습니다.

파이프 함수가 실제로 무엇인지 정의하는 것부터 시작하겠습니다.그 핵심은 표준적인 '인사이드 아웃' 순서가 아니라 일련의 함수 호출을 논리적인 순서로 표현하는 방식일 뿐입니다.

예를 들어 다음과 같은 기능을 살펴봅니다.

def one(value):
  return value

def two(value):
  return 2*value

def three(value):
  return 3*value

별로 흥미롭지는 않지만, 흥미로운 일들이 일어난다고 가정해 보세요.value 우리는 그들을 순서대로 부르고, 각각의 출력을 다음에 전달하고자 합니다.바닐라 파이썬에서는 다음과 같습니다.

result = three(two(one(1)))

이것은 믿을 수 없을 정도로 읽을 수 없으며 더 복잡한 파이프라인의 경우 더 악화될 것입니다.따라서, 여기에 초기 인수를 사용하는 간단한 파이프 함수와 이를 적용하기 위한 일련의 함수가 있습니다.

def pipe(first, *args):
  for fn in args:
    first = fn(first)
  return first

이렇게 부르자.

result = pipe(1, one, two, three)

그것은 제가 보기에 매우 읽을 수 있는 '파이프' 구문으로 보입니다 :).오버로딩 오퍼레이터나 그런 것보다 판독성이 떨어지는 것은 없습니다.사실, 나는 그것이 더 읽기 쉬운 파이썬 코드라고 주장하고 싶습니다.

OP의 예를 해결하는 보잘것없는 파이프는 다음과 같습니다.

from math import sqrt
from datetime import datetime

def as_date(s):
  return datetime.strptime(s, '%Y-%m-%d')

def as_character(value):
  # Do whatever as.character does
  return value

pipe("2014-01-01", as_date)
pipe(12, sqrt, lambda x: x**2, as_character)

pipe와 함께Infix

실뱅 르루가 암시한 것처럼, 우리가 사용할 수 있는 것은Infixinfix를 구성하는 연산자pipe 이루어지는지 이것이 어떻게 이루어지는지 보겠습니다.

먼저, 토머 필리바의 코드입니다.

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 연산자로서 호출 기능에 새로운 "syntax"를 사용할 수 있습니다.

>>> @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'

부분 적용에 관한 참고 사항

%>%오퍼레이터 fromdpylr함수의 첫번째 인수를 통해 인수를 밀어넣습니다.

df %>% 
filter(x >= 2) %>%
mutate(y = 2*x)

에 해당하는

df1 <- filter(df, x >= 2)
df2 <- mutate(df1, y = 2*x)

파이썬에서 비슷한 것을 얻는 가장 쉬운 방법은 카레링을 사용하는 것입니다.toolz도서관은 a를 제공합니다.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)

주의: 주장마지막 주장 위치 밀어 넣는 것, 즉

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

다른 infix 구분 기호 사용

다른 Python 연산자 메서드를 재정의하여 Infix 호출을 둘러싼 기호를 변경할 수 있습니다.예를 들어, 스위칭__or__그리고.__ror__.__mod__그리고.__rmod__변경할 것입니다.|에 대한 교환원.mod교환입니다.

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

내 2c 추가.저는 개인적으로 기능적인 스타일 프로그래밍을 위해 패키지 fn을 사용합니다.당신의 예는 다음과 같이 해석됩니다.

from fn import F, _
from math import sqrt

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

F는 부분적인 적용과 구성을 위해 기능적인 스타일의 통사당을 가진 포장지 클래스입니다._는 익명 함수들을 위한 스칼라 스타일의 컨스트럭터입니다 (파이썬의 컨스트럭터와 유사함)lambda); 변수를 나타내므로 여러 개를 조합할 수 있습니다._더 많은 인수를 가진 함수를 얻기 위해 하나의 식 안에 객체들(예:_ + _와 동치입니다.lambda a, b: a + b).F(sqrt) >> _**2 >> str결과적으로 A가 됩니다.Callable원하는 횟수만큼 사용할 수 있는 오브젝트.

한 가지 대안은 워크플로 도구 데스크를 사용하는 것입니다.비록 통사적으로 재미있지는 않지만...

var
| do this
| then do that

...그렇기 때문에 변수가 연쇄적으로 흘러갈 수 있고, 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 작업 후 파이썬에서 파이핑 패턴을 사용하고 싶었습니다.이는 완전히 동일한 패턴은 아니지만 유사하며 앞서 말한 것처럼 병렬화의 추가적인 이점이 함께 제공됩니다. 다른 사람이 먼저 실행하는 것에 의존하지 않는 작업을 워크플로우에서 가져오라고 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']

아주 좋습니다.pipemodule here https://pypi.org/project/pipe/ 그것은 오버로드 | 연산자 그리고 많은 파이프 기능을 제공합니다.add, first, where, tail기타.

>>> [1, 2, 3, 4] | where(lambda x: x % 2 == 0) | add
6

>>> sum([1, [2, 3], 4] | traverse)
10

또한 파이프 기능을 직접 작성하는 것은 매우 쉽습니다.

@Pipe
def p_sqrt(x):
    return sqrt(x)

@Pipe
def p_pr(x):
    print(x)

9 | p_sqrt | p_pr

파이프 기능은 점으로 팬더 방법을 구성하여 달성할 수 있습니다.아래 예시가 있습니다.

샘플 데이터 프레임 로드:

import seaborn    
iris = seaborn.load_dataset("iris")
type(iris)
# <class 'pandas.core.frame.DataFrame'>

점을 사용하여 팬더 방법의 구성을 설명합니다.

(iris.query("species == 'setosa'")
     .sort_values("petal_width")
     .head())

필요한 경우 팬더 데이터 프레임에 새로운 방법을 추가할 수 있습니다(예: 여기서 수행).

pandas.DataFrame.new_method  = new_method

그냥 사용.cool.

먼저 달려라python -m pip install cool. 그럼 달려보세요python.

from cool import F

range(10) | F(filter, lambda x: x % 2) | F(sum) == 25

더 많은 사용법을 얻으려면 https://github.com/abersheeran/cool 을 읽을 수 있습니다.

나의 2센트는 http://tomerfiliba.com/blog/Infix-Operators/ 에서 영감을 얻었습니다.

class FuncPipe:
  class Arg:
    def __init__(self, arg):
      self.arg = arg
    def __or__(self, func):
      return func(self.arg)

  def __ror__(self, arg):
    return self.Arg(arg)
pipe = FuncPipe()

그리고나서

1 |pipe| \
  (lambda x: return x+1) |pipe| \
  (lambda x: return 2*x)

돌아온다

4 

언급URL : https://stackoverflow.com/questions/28252585/functional-pipes-in-python-like-from-rs-magrittr

반응형