Numpy.median.reduceat জন্য দ্রুত বিকল্প


12

এই উত্তরের সাথে সম্পর্কিত , একটি অ্যারেতে মিডিয়ানদের গণনা করার জন্য একটি দ্রুত উপায় আছে যার সাথে গ্রুপ রয়েছে অসম সংখ্যার উপাদানগুলির?

উদাহরণ:

data =  [1.00, 1.05, 1.30, 1.20, 1.06, 1.54, 1.33, 1.87, 1.67, ... ]
index = [0,    0,    1,    1,    1,    1,    2,    3,    3,    ... ]

এবং তারপর আমি নম্বর এবং গ্রুপ প্রতি মধ্যমা মধ্যে পার্থক্য গনা (গ্রুপ এর উদাঃ মধ্যমা চান 0হয় 1.025তাই প্রথম ফল 1.00 - 1.025 = -0.025)। সুতরাং উপরের অ্যারের জন্য, ফলাফলগুলি প্রদর্শিত হবে:

result = [-0.025, 0.025, 0.05, -0.05, -0.19, 0.29, 0.00, 0.10, -0.10, ...]

থেকে np.median.reduceat অস্তিত্ব নেই (এখনও), তাই এটি অর্জনের জন্য আরও একটি দ্রুত উপায় আছে? আমার অ্যারেটিতে কয়েক মিলিয়ন সারি থাকবে যাতে গতি অত্যন্ত গুরুত্বপূর্ণ!

সূচকগুলি সংবিধিবদ্ধ এবং আদেশযুক্ত হিসাবে ধরে নেওয়া যেতে পারে (সেগুলি না থাকলে তাদের রূপান্তর করা সহজ)।


পারফরম্যান্স তুলনার জন্য ডেটা উদাহরণ:

import numpy as np

np.random.seed(0)
rows = 10000
cols = 500
ngroup = 100

# Create random data and groups (unique per column)
data = np.random.rand(rows,cols)
groups = np.random.randint(ngroup, size=(rows,cols)) + 10*np.tile(np.arange(cols),(rows,1))

# Flatten
data = data.ravel()
groups = groups.ravel()

# Sort by group
idx_sort = groups.argsort()
data = data[idx_sort]
groups = groups[idx_sort]

লিঙ্কিত scipy.ndimage.medianউত্তরে আপনি কি পরামর্শটি দিয়েছিলেন? এটি আমার কাছে মনে হয় না যে এটির প্রতি লেবেল সমান সংখ্যক উপাদান দরকার। নাকি আমি কিছু মিস করেছি?
Andras Deak

সুতরাং, যখন আপনি কয়েক মিলিয়ন সারি বলেছেন, আপনার আসল ডেটাসেটটি কি 2D অ্যারে এবং আপনি এই সারিগুলির প্রতিটিতে এই অপারেশনটি করছেন?
দিবাকর

@ দিবাকর তথ্য পরীক্ষার জন্য প্রশ্নের সম্পাদনা দেখুন
জিন-পল

আপনি প্রাথমিক ডেটাতে ইতিমধ্যে বেনমার্ক দিয়েছিলেন, ফর্ম্যাটটি একই রাখতে আমি এটি স্ফীত করেছিলাম। আমার স্ফীতিত ডেটাসেটের বিপরীতে সমস্ত কিছুই বেনমার্কযুক্ত। এটি এখনই পরিবর্তন করা যুক্তিসঙ্গত নয়
রোজগোশ

উত্তর:


7

কখনও কখনও আপনার অ-অহংকারমূলক নিম্পি কোডটি লিখতে হবে যদি আপনি সত্যিই আপনার গণনাটি গতি বাড়িয়ে তুলতে চান যা আপনি দেশী আঙ্কুল দিয়ে না করতে পারেন।

numbaআপনার পাইথন কোডটি নিম্ন-স্তরের সি তে সংকলন করে যেহেতু প্রচুর আর্দ্রতা নিজেই সাধারণত সি এর মতো দ্রুত হয় তাই আপনার সমস্যাটি যদি আঙ্কুর সাথে দেশীয় ভেক্টরাইজেশনকে ndণ না দেয় তবে এটি বেশিরভাগ ক্ষেত্রেই কার্যকর হয়ে যায়। এটি একটি উদাহরণ (যেখানে আমি ধরে নিয়েছি যে সূচকগুলি সুসংগত এবং সাজানো রয়েছে, যা উদাহরণের ডেটাতেও প্রতিফলিত হয়):

import numpy as np
import numba

# use the inflated example of roganjosh https://stackoverflow.com/a/58788534
data =  [1.00, 1.05, 1.30, 1.20, 1.06, 1.54, 1.33, 1.87, 1.67]
index = [0,    0,    1,    1,    1,    1,    2,    3,    3] 

data = np.array(data * 500) # using arrays is important for numba!
index = np.sort(np.random.randint(0, 30, 4500))               

# jit-decorate; original is available as .py_func attribute
@numba.njit('f8[:](f8[:], i8[:])') # explicit signature implies ahead-of-time compile
def diffmedian_jit(data, index): 
    res = np.empty_like(data) 
    i_start = 0 
    for i in range(1, index.size): 
        if index[i] == index[i_start]: 
            continue 

        # here: i is the first _next_ index 
        inds = slice(i_start, i)  # i_start:i slice 
        res[inds] = data[inds] - np.median(data[inds]) 

        i_start = i 

    # also fix last label 
    res[i_start:] = data[i_start:] - np.median(data[i_start:])

    return res

আইপিথনের %timeitযাদু ব্যবহার করে এখানে কিছু সময় দেওয়া হল :

>>> %timeit diffmedian_jit.py_func(data, index)  # non-jitted function
... %timeit diffmedian_jit(data, index)  # jitted function
...
4.27 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
65.2 µs ± 1.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

প্রশ্নগুলিতে আপডেট হওয়া উদাহরণস্বরূপ ডেটা ব্যবহার করে এই সংখ্যাগুলি (উদাহরণস্বরূপ পাইথন ফাংশনের রানটাইম বনাম জেআইটি-এক্সিলারেটড ফান্টিটিওর রানটাইম) হ'ল

>>> %timeit diffmedian_jit.py_func(data, groups) 
... %timeit diffmedian_jit(data, groups)
2.45 s ± 34.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
93.6 ms ± 518 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

এটি ক্ষুদ্র ক্ষেত্রে 65x স্পিডআপ এবং বৃহত্তর ক্ষেত্রে 26x গতিসম্পন্ন (অবশ্যই ধীর লুপী কোডের সাথে তুলনা করে) ত্বকযুক্ত কোড ব্যবহার করে। আরেকটি উত্সাহটি হ'ল (নেটিভ নিম্পির সাথে আদর্শ ভেক্টরাইজেশনের বিপরীতে) আমাদের এই গতি অর্জনের জন্য অতিরিক্ত মেমরির প্রয়োজন নেই, এটি সর্বোত্তম এবং সংকলিত নিম্ন-স্তরের কোড যা চালানো শেষ হয় about


উপরের ফাংশনটি অনুমান করে যে নাম্পি ইনট অ্যারেগুলি int64 ডিফল্টরূপে হয় যা উইন্ডোজে আসলে হয় না। সুতরাং একটি বিকল্প হ'ল কল থেকে স্বাক্ষর সরিয়ে ফেলা numba.njit, যথাযথ ইন-টাইম সংকলন ট্রিগার করে। তবে এর অর্থ এই যে ফাংশনটি প্রথম নির্বাহের সময় সংকলিত হবে, যা সময়সীমার ফলাফলের সাথে হস্তক্ষেপ করতে পারে (আমরা হয় প্রতিনিধি ডেটা ধরণের সাহায্যে ম্যানুয়ালি একবার ফাংশনটি সম্পাদন করতে পারি, বা কেবল গ্রহণ করতে পারি যে প্রথম সময় নির্বাহের কাজটি অনেক ধীর হবে, যা হওয়া উচিত) অবহেলা করা)। এটি হ'ল আমি হ'ল একটি স্বাক্ষর নির্দিষ্ট করে বাধা দেওয়ার চেষ্টা করেছি, যা সময়ের-সংকলনকে ট্রিগার করে।

যাইহোক, সঠিকভাবে জেআইটি ক্ষেত্রে ডেকরেটারটি আমাদের দরকার need

@numba.njit
def diffmedian_jit(...):

নোট করুন যে জিট-সংকলিত ফাংশনের জন্য আমি উপরে বর্ণিত সময়গুলি কেবলমাত্র ফাংশনটি সংকলিত হয়ে গেলে প্রয়োগ করা হয়। এটি হয় সংজ্ঞায় হয় (উত্সাহ সংকলন সহ, যখন একটি স্পষ্ট স্বাক্ষর প্রেরণ করা হয় numba.njit), বা প্রথম ফাংশন কল চলাকালীন (অলস সংকলন সহ, যখন কোনও স্বাক্ষর পাস করা হয় না)numba.njit )। যদি ফাংশনটি কেবল একবার সম্পাদন হতে চলেছে তবে এই পদ্ধতির গতির জন্য সংকলনের সময়টিও বিবেচনা করা উচিত। সংকলন + নির্বাহের মোট সময় অসম্পূর্ণ রানটাইমের চেয়ে কম হলে (সাধারণত উপরের ক্ষেত্রে এটি সত্য, যেখানে নেটিভ পাইথন ফাংশনটি খুব ধীর) এটি সাধারণত ফাংশন সংকলন করার উপযুক্ত। এটি বেশিরভাগ ক্ষেত্রে ঘটে যখন আপনি আপনার সংকলিত ফাংশনটিকে অনেকবার কল করছেন।

হিসাবে max9111 একটি মন্তব্যে উল্লেখ করা হয়েছে, এক গুরুত্বপূর্ণ বৈশিষ্ট্য numbaহল cacheশব্দ থেকে jit। পাসিং cache=Trueকরতে numba.jit, ডিস্কে কম্পাইল ফাংশন সংরক্ষণ করবে যাতে দেওয়া পাইথন মডিউল পরবর্তী সম্পাদনের সময় ফাংশন থেকে সেখানে বদলে recompiled, যা আবার অনাবশ্যক করতে পারেন দীর্ঘ রান রানটাইম লোড হবে।


@ দিবাকর, প্রকৃতপক্ষে, এটি সূচকগুলি সুসংগত এবং সাজানো বলে ধরেছে, যা ওপির ডেটাতে অনুমানের মতো বলে মনে হয়েছিল এবং স্বয়ংক্রিয়ভাবে রঞ্জগঞ্জের indexডেটাতে অন্তর্ভুক্ত ছিল । আমি এই সম্পর্কে একটি নোট রেখে দেব, ধন্যবাদ :)
আন্দ্রেস ডেক

ঠিক আছে, সামঞ্জস্যতা স্বয়ংক্রিয়ভাবে অন্তর্ভুক্ত করা হয় না ... তবে আমি নিশ্চিত যে এটি যাইহোক সামঞ্জস্যপূর্ণ হতে হবে। হুম ...
আন্দ্রেস ডেক

1
@ আন্দ্রেসডিক লেবেলগুলি সুসংহত এবং সাজানো (ধরে রাখা সহজ তবে তা ঠিক করা)
জিন-পল

1
@ অ্যান্ড্রাসডিক তথ্য পরীক্ষার জন্য প্রশ্নের সম্পাদনা দেখুন (যেমন প্রশ্নগুলির মধ্যে পারফরম্যান্সের তুলনাটি সামঞ্জস্যপূর্ণ)
জিন-পল

1
cache=Trueদোভাষীর প্রতিটি পুনঃসূচনা পুনরায় সংকলন এড়াতে আপনি কীওয়ার্ডটি উল্লেখ করতে পারেন।
সর্বোচ্চ 9111

5

একটি পদ্ধতির Pandasবিশুদ্ধরূপে ব্যবহার করতে এখানে ব্যবহার করা হবে groupby। সময়গুলির আরও ভাল ধারণা দেওয়ার জন্য আমি ইনপুট আকারকে কিছুটা স্ফীত করেছি (যেহেতু ডিএফ তৈরির ক্ষেত্রে ওভারহেড রয়েছে)।

import numpy as np
import pandas as pd

data =  [1.00, 1.05, 1.30, 1.20, 1.06, 1.54, 1.33, 1.87, 1.67]
index = [0,    0,    1,    1,    1,    1,    2,    3,    3]

data = data * 500
index = np.sort(np.random.randint(0, 30, 4500))

def df_approach(data, index):
    df = pd.DataFrame({'data': data, 'label': index})
    df['median'] = df.groupby('label')['data'].transform('median')
    df['result'] = df['data'] - df['median']

নিম্নলিখিত দেয় timeit:

%timeit df_approach(data, index)
5.38 ms ± 50.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

একই নমুনা আকারের জন্য, আমি আরিয়েরেজের ডিক পদ্ধতির হতে পারি:

%timeit dict_approach(data, index)
8.12 ms ± 3.47 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

তবে, যদি আমরা ইনপুটগুলি 10 এর আরও একটি ফ্যাক্টর দ্বারা বৃদ্ধি করি তবে সময়গুলি হয়ে যায়:

%timeit df_approach(data, index)
7.72 ms ± 85 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit dict_approach(data, index)
30.2 ms ± 10.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

যাইহোক, কিছু reability ব্যয় দ্বারা উত্তর Divakar বিশুদ্ধ numpy ব্যবহারে আসে:

%timeit bin_median_subtract(data, index)
573 µs ± 7.48 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

নতুন ডেটাসেটের আলোকে (যা সত্যিই শুরুতে সেট করা উচিত ছিল):

%timeit df_approach(data, groups)
472 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit bin_median_subtract(data, groups) #https://stackoverflow.com/a/58788623/4799172
3.02 s ± 31.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit dict_approach(data, groups) #https://stackoverflow.com/a/58788199/4799172
<I gave up after 1 minute>

# jitted (using @numba.njit('f8[:](f8[:], i4[:]') on Windows) from  https://stackoverflow.com/a/58788635/4799172
%timeit diffmedian_jit(data, groups)
132 ms ± 3.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

এই উত্তরের জন্য আপনাকে ধন্যবাদ! অন্যান্য উত্তরগুলির সাথে সামঞ্জস্য করার জন্য, আপনি কি আমার প্রশ্নের সম্পাদনার জন্য সরবরাহ করা উদাহরণ ডেটাতে আপনার সমাধানগুলি পরীক্ষা করতে পারেন?
জিন-পল

@ জিন পল এর সময় ইতিমধ্যে সামঞ্জস্যপূর্ণ, না? তারা আমার প্রাথমিক বেঞ্চমার্ক ডেটা ব্যবহার করেছিল এবং যে ক্ষেত্রে তারা দেয় নি, আমি তাদের জন্য একই মানদণ্ডের সময় সরবরাহ করেছি
roojosh

আমি উপেক্ষা করেছি আপনি ইতিমধ্যে দিবাকরের উত্তরের একটি রেফারেন্স যুক্ত করেছেন সুতরাং আপনার উত্তরটি ইতিমধ্যে বিভিন্ন পদ্ধতির মধ্যে একটি দুর্দান্ত তুলনা করেছে, এর জন্য ধন্যবাদ!
জিন-পল

1
@ জিন-পল আমি সর্বশেষতম সময়গুলি নীচে যুক্ত করেছেন যেহেতু এটি আসলে জিনিসগুলিকে বেশ
মারাত্মকভাবে

1
প্রশ্ন পোস্ট করার সময় পরীক্ষার সেটটি যোগ না করার জন্য ক্ষমাপ্রার্থী, আপনি এখনও পরীক্ষার ফলাফল যুক্ত করেছেন এমন প্রশংসা করেছেন! ধন্যবাদ !!!
জিন-পল

4

হতে পারে আপনি ইতিমধ্যে এটি করেছেন, তবে তা না হলে দেখুন এটি যথেষ্ট দ্রুত কিনা:

median_dict = {i: np.median(data[index == i]) for i in np.unique(index)}
def myFunc(my_dict, a): 
    return my_dict[a]
vect_func = np.vectorize(myFunc)
median_diff = data - vect_func(median_dict, index)
median_diff

আউটপুট:

array([-0.025,  0.025,  0.05 , -0.05 , -0.19 ,  0.29 ,  0.   ,  0.1  ,
   -0.1  ])

সুস্পষ্ট জানায় ঝুঁকি, np.vectorizeএকটি হল খুব একটি লুপ জন্য পাতলা মোড়কের, তাই আমি আশা করবেন না এই পদ্ধতির বিশেষ করে ফাস্ট যাবে।
আন্দ্রেস ডেক

1
@ আন্দ্রেসডিক আমি একমত নই :) আমি অনুসরণ করব এবং যদি কেউ এর থেকে আরও ভাল সমাধান পোস্ট করে তবে আমি তা মুছে ফেলব।
আর্যরেজ

1
আমি মনে করি না দ্রুত পপ আপ হওয়া সত্ত্বেও আপনি এটি মুছতে হবে :)
Andras Deak

@roganjosh যে সম্ভবত কারণ আপনি সংজ্ঞায়িত হয়নি dataএবং indexযেমন np.arrayপ্রশ্ন হিসেবে সে।
আরিরেজ

1
@ জিন-পল রঞ্জোগোষ আমার এবং তার পদ্ধতির মধ্যে সময়ের তুলনা করেছিলেন এবং অন্যরা এখানে তাদের তুলনা করেছেন। এটি কম্পিউটার হার্ডওয়ারের উপর নির্ভর করে, সুতরাং প্রত্যেকের নিজস্ব পদ্ধতি পরীক্ষা করার কোনও মানে নেই, তবে এটি এখানে সবচেয়ে ধীর সমাধানটি নিয়ে এসেছি বলে মনে হয়।
আর্যরেজ

4

ধনাত্মক বিন / সূচক মানগুলির জন্য বিন্যাস-মধ্যমা পেতে এখানে নুমপি ভিত্তিক একটি পদ্ধতি রয়েছে -

def bin_median(a, i):
    sidx = np.lexsort((a,i))

    a = a[sidx]
    i = i[sidx]

    c = np.bincount(i)
    c = c[c!=0]

    s1 = c//2

    e = c.cumsum()
    s1[1:] += e[:-1]

    firstval = a[s1-1]
    secondval = a[s1]
    out = np.where(c%2,secondval,(firstval+secondval)/2.0)
    return out

বিয়োগফলের আমাদের নির্দিষ্ট ক্ষেত্রে সমাধান করতে -

def bin_median_subtract(a, i):
    sidx = np.lexsort((a,i))

    c = np.bincount(i)

    valid_mask = c!=0
    c = c[valid_mask]    

    e = c.cumsum()
    s1 = c//2
    s1[1:] += e[:-1]
    ssidx = sidx.argsort()
    starts = c%2+s1-1
    ends = s1

    starts_orgindx = sidx[np.searchsorted(sidx,starts,sorter=ssidx)]
    ends_orgindx  = sidx[np.searchsorted(sidx,ends,sorter=ssidx)]
    val = (a[starts_orgindx] + a[ends_orgindx])/2.
    out = a-np.repeat(val,c)
    return out

খুব সুন্দর উত্তর! উদাহরণস্বরূপ গতির উন্নতি সম্পর্কে আপনার কি কোনও ইঙ্গিত রয়েছে df.groupby('index').transform('median')?
জিন পল

@ জিন-পল আপনি কি আপনার মিলিয়ন মিলিয়ন ডেটাসেটের পরীক্ষা করতে পারবেন?
দিবাকর

ডেটা পরীক্ষার জন্য প্রশ্নের সম্পাদনা দেখুন
জিন পল

@ জিন পল একটি সহজ সমাধানের জন্য আমার সমাধান সম্পাদনা করেছেন। আপনি যদি হন তবে এটি পরীক্ষার জন্য ব্যবহার করতে ভুলবেন না।
দিবাকর
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.