Квантильная нормализация в кадре данных pandas

Проще говоря, как применить квантильную нормализацию к большому кадру данных Pandas (вероятно, 2 000 000 строк) в Python?

PS. Я знаю, что есть пакет с именем rpy2, который может запускать R в подпроцессе, используя нормализацию квантилей в R. Но правда в том, что R не может вычислить правильный результат, когда я использую набор данных, как показано ниже:

5.690386092696389541e-05,2.051450375415418849e-05,1.963190184049079707e-05,1.258362869906251862e-04,1.503352476021528139e-04,6.881341586355676286e-06
8.535579139044583634e-05,5.128625938538547123e-06,1.635991820040899643e-05,6.291814349531259308e-05,3.006704952043056075e-05,6.881341586355676286e-06
5.690386092696389541e-05,2.051450375415418849e-05,1.963190184049079707e-05,1.258362869906251862e-04,1.503352476021528139e-04,6.881341586355676286e-06
2.845193046348194770e-05,1.538587781561563968e-05,2.944785276073619561e-05,4.194542899687506431e-05,6.013409904086112150e-05,1.032201237953351358e-05

Редактировать:

Что я хочу:

Учитывая приведенные выше данные, как применить квантильную нормализацию, следуя шагам в https://en.wikipedia.org/wiki/Quantile_normalization.

Я нашел фрагмент кода на Python, в котором говорилось, что он может вычислять квантильную нормализацию:

import rpy2.robjects as robjects
import numpy as np
from rpy2.robjects.packages import importr
preprocessCore = importr('preprocessCore')


matrix = [ [1,2,3,4,5], [1,3,5,7,9], [2,4,6,8,10] ]
v = robjects.FloatVector([ element for col in matrix for element in col ])
m = robjects.r['matrix'](v, ncol = len(matrix), byrow=False)
Rnormalized_matrix = preprocessCore.normalize_quantiles(m)
normalized_matrix = np.array( Rnormalized_matrix)

Код отлично работает с образцами данных, используемыми в коде, однако, когда я тестировал его с данными, приведенными выше, результат был неправильным.

Поскольку ryp2 предоставляет интерфейс для запуска R в подпроцессе python, я снова протестировал его непосредственно в R, и результат все еще был неправильным. В результате я думаю, что причина в том, что метод в R неверен.


person Shawn. L    schedule 21.06.2016    source источник
comment
Я удалил тег R, поскольку вы (1) не используете R и (2) не хотите, чтобы R был в ответе. Но если вы говорите, что R не может вычислить правильный результат, это звучит так, будто вы либо пренебрежительно относитесь к R (с какой целью?), либо хотите, чтобы кто-то поправил ваш неопубликованный код. В любом случае, возможно, я неправильно понимаю, что вы хотите: для нормализации квантилей требуется исходное и целевое распределение, и я не уверен, что вы здесь предоставляете. Можете ли вы уточнить, пожалуйста?   -  person r2evans    schedule 21.06.2016
comment
@ r2evans Спасибо за ваш комментарий, и я уже отредактировал вопрос. К вашему сведению, код, который я искал в Google, запускает R как подпроцесс Python. После прямого запуска R я обнаружил, что результат был неправильным. Кроме того, я не совсем понимаю, что вы подразумеваете под «целевым распространением». Согласно Вики, вычисление квантильной нормализации не включает этот термин. Вопрос, надеюсь, я ясно дал понять, заключается в том, чтобы применить квантильную нормализацию к данным, которые я дал.   -  person Shawn. L    schedule 21.06.2016
comment
Вы правы, мой термин цели не очень хорош. В вики упоминается создание двух одинаковых дистрибутивов, поэтому мне интересно, какие у вас два дистрибутива. Теперь, когда вы предоставили дополнительный код (и данные, определенные как matrix), я не понимаю, какие ваши фактические данные должны быть количественно нормированы. (Возможно, глупый вопрос, но возможно ли, что матрица переставлена ​​по сравнению с тем, что вам действительно нужно?)   -  person r2evans    schedule 21.06.2016
comment
@ r2evans Прошу прощения за путаницу, которую я вызвал. К вашему сведению, фактические данные представляют собой матрицу (2119055,124). Данные, которые я привел выше, являются крошечной частью их для тестирования. И да, я рассматривал вопрос транспонирования. Как вы могли видеть, в примере кода матрица равна (3,5), но нормализованный результат равен (5,3), поэтому я резюмировал, что для использования этого кода мне нужно сначала транспонировать матрицу. Чтобы быть более ясным, мои данные (4,6), и для использования кода я назначу транспонированные данные, то есть (6,4), переменной matrix, а затем продолжу.   -  person Shawn. L    schedule 21.06.2016


Ответы (8)


Используя пример набора данных из статьи Википедии:

df = pd.DataFrame({'C1': {'A': 5, 'B': 2, 'C': 3, 'D': 4},
                   'C2': {'A': 4, 'B': 1, 'C': 4, 'D': 2},
                   'C3': {'A': 3, 'B': 4, 'C': 6, 'D': 8}})

df
Out: 
   C1  C2  C3
A   5   4   3
B   2   1   4
C   3   4   6
D   4   2   8

Для каждого ранга среднее значение можно рассчитать следующим образом:

rank_mean = df.stack().groupby(df.rank(method='first').stack().astype(int)).mean()

rank_mean
Out: 
1    2.000000
2    3.000000
3    4.666667
4    5.666667
dtype: float64

Затем полученный ряд rank_mean можно использовать в качестве сопоставления для рангов, чтобы получить нормализованные результаты:

df.rank(method='min').stack().astype(int).map(rank_mean).unstack()
Out: 
         C1        C2        C3
A  5.666667  4.666667  2.000000
B  2.000000  2.000000  3.000000
C  3.000000  4.666667  4.666667
D  4.666667  3.000000  5.666667
person ayhan    schedule 10.12.2016
comment
элегантное использование groupby, map и stacking/unstacking. вы pandas разработчик? - person O.rka; 09.01.2017
comment
Спасибо. Нет, я обычный пользователь. - person ayhan; 10.01.2017
comment
@ayhan Почему вы использовали разные методы ранжирования в первой и второй строке обработки, то есть first против min? - person Mischa Lisovyi; 19.08.2019
comment
Просто указываю (и саморекламирую), что это не дает правильных результатов согласно Википедии. Я реализовал быстрый метод, который дает правильные результаты и устанавливается с помощью conda или pip: stackoverflow.com/a/62792272/9544516 - person Maarten-vd-Sande; 02.08.2020

Хорошо, я реализовал метод относительно высокой эффективности.

После завершения эта логика кажется довольно простой, но, тем не менее, я решил опубликовать ее здесь для тех, кто чувствует себя сбитым с толку, как я был, когда я не мог погуглить доступный код.

Код находится на github: Quantile Normalize

person Shawn. L    schedule 22.06.2016

Стоит отметить, что и код Айхана, и код Шона используют меньшее среднее значение ранга для связей, но если вы используете normalize.quantiles() пакета R, он будет использовать среднее значение рангов для связей.

Используя приведенный выше пример:

> df

   C1  C2  C3
A   5   4   3
B   2   1   4
C   3   4   6
D   4   2   8

> normalize.quantiles(as.matrix(df))

         C1        C2        C3
A  5.666667  5.166667  2.000000
B  2.000000  2.000000  3.000000
C  3.000000  5.166667  4.666667
D  4.666667  3.000000  5.666667
person msg    schedule 03.01.2020
comment
Просто хочу сказать, что я сделал пакет/ответ под названием qnorm для Python, который обрабатывает связи: stackoverflow.com/a/62792272/9544516< /а> - person Maarten-vd-Sande; 08.07.2020

Код ниже дает тот же результат, что и preprocessCore::normalize.quantiles.use.target, и я считаю его более простым и понятным, чем приведенные выше решения. Также производительность должна быть хорошей до огромных длин массивов.

import numpy as np

def quantile_normalize_using_target(x, target):
    """
    Both `x` and `target` are numpy arrays of equal lengths.
    """

    target_sorted = np.sort(target)

    return target_sorted[x.argsort().argsort()]

Если у вас есть pandas.DataFrame легко сделать:

quantile_normalize_using_target(df[0].as_matrix(),
                                df[1].as_matrix())

(Нормализация первого столбца ко второму в качестве эталонного распределения в приведенном выше примере.)

person deeenes    schedule 02.05.2017

Это незначительная корректировка, но я полагаю, что многие заметили тонкий «недостаток» в ответе @ayhan.

Я внес в него небольшую корректировку, которая дает «правильный» ответ, при этом не прибегая к каким-либо внешним библиотекам для такой чрезвычайно простой функции.

Единственная необходимая корректировка — это раздел [Add interpolated values].

import pandas as pd

df = pd.DataFrame({'C1': {'A': 5, 'B': 2, 'C': 3, 'D': 4},
                   'C2': {'A': 4, 'B': 1, 'C': 4, 'D': 2},
                   'C3': {'A': 3, 'B': 4, 'C': 6, 'D': 8}})

def quant_norm(df):
    ranks = (df.rank(method="first")
              .stack())
    rank_mean = (df.stack()
                   .groupby(ranks)
                   .mean())
    # Add interpolated values in between ranks
    finer_ranks = ((rank_mean.index+0.5).to_list() +
                    rank_mean.index.to_list())
    rank_mean = rank_mean.reindex(finer_ranks).sort_index().interpolate()
    return (df.rank(method='average')
              .stack()
              .map(rank_mean)
              .unstack())
quant_norm(df)

Out[122]: 
         C1        C2        C3
A  5.666667  5.166667  2.000000
B  2.000000  2.000000  3.000000
C  3.000000  5.166667  4.666667
D  4.666667  3.000000  5.666667
person chase    schedule 19.05.2021

Возможно, более надежно использовать медиану в каждой строке, а не среднее значение (на основе кода от Шона. Л):

def quantileNormalize(df_input):
    df = df_input.copy()
    #compute rank
    dic = {}
    for col in df:
        dic[col] = df[col].sort_values(na_position='first').values
    sorted_df = pd.DataFrame(dic)
    #rank = sorted_df.mean(axis = 1).tolist()
    rank = sorted_df.median(axis = 1).tolist()
    #sort
    for col in df:
        # compute percentile rank [0,1] for each score in column 
        t = df[col].rank( pct=True, method='max' ).values
        # replace percentile values in column with quantile normalized score
        # retrieve q_norm score using calling rank with percentile value
        df[col] = [ np.nanpercentile( rank, i*100 ) if ~np.isnan(i) else np.nan for i in t ]
    return df
person xspensiv    schedule 06.04.2017

Я новичок в пандах и опаздываю на вопрос, но я думаю, что ответ также может быть полезен. Он основан на отличном ответе от @ayhan:

def quantile_normalize(dataframe, cols, pandas=pd):

    # copy dataframe and only use the columns with numerical values
    df = dataframe.copy().filter(items=cols)

    # columns from the original dataframe not specified in cols
    non_numeric = dataframe.filter(items=list(filter(lambda col: col not in cols, list(dataframe))))


    rank_mean = df.stack().groupby(df.rank(method='first').stack().astype(int)).mean()  

    norm = df.rank(method='min').stack().astype(int).map(rank_mean).unstack()


    result = pandas.concat([norm, non_numeric], axis=1)
    return result

основное отличие здесь ближе к некоторым приложениям реального мира. Часто у вас есть просто матрицы числовых данных, и в этом случае исходного ответа достаточно.

Иногда у вас также есть текстовые данные. Это позволяет вам указать столбцы cols ваших числовых данных и запустить квантильную нормализацию для этих столбцов. В конце он объединит нечисловые (или не нормализованные) столбцы из исходного фрейма данных.

например если вы добавили некоторые «метаданные» (char) в пример вики:

df = pd.DataFrame({
    'rep1': [5, 2, 3, 4],
    'rep2': [4, 1, 4, 2],
    'rep3': [3, 4, 6, 8],
    'char': ['gene_a', 'gene_b', 'gene_c', 'gene_d']
}, index = ['a', 'b', 'c', 'd'])

тогда вы можете позвонить

quantile_normalize(t, ['rep1', 'rep2', 'rep3'])

получить

    rep1        rep2        rep3        char
a   5.666667    4.666667    2.000000    gene_a
b   2.000000    2.000000    3.000000    gene_b
c   3.000000    4.666667    4.666667    gene_c
d   4.666667    3.000000    5.666667    gene_d
person SumNeuron    schedule 14.01.2018

Как указывает @msg, ни одно из решений здесь не учитывает связи. Я сделал пакет Python под названием qnorm, который обрабатывает связи и правильно воссоздает Пример квантильной нормализации из Википедии:

import pandas as pd
import qnorm

df = pd.DataFrame({'C1': {'A': 5, 'B': 2, 'C': 3, 'D': 4},
                   'C2': {'A': 4, 'B': 1, 'C': 4, 'D': 2},
                   'C3': {'A': 3, 'B': 4, 'C': 6, 'D': 8}})

print(qnorm.quantile_normalize(df))
         C1        C2        C3
A  5.666667  5.166667  2.000000
B  2.000000  2.000000  3.000000
C  3.000000  5.166667  4.666667
D  4.666667  3.000000  5.666667

Установка может быть выполнена с помощью pip или conda

pip install qnorm

or

conda config --add channels conda-forge
conda install qnorm
person Maarten-vd-Sande    schedule 08.07.2020
comment
Чем это отличается от указания method='average' в его df.rank()? - person Sos; 18.08.2020
comment
@Sos Я не могу четко помещать кадры данных в комментарии, но почему бы вам не попробовать? Я получаю разные результаты с method='average' и qnorm. Он просто по-другому разрешает связи. - person Maarten-vd-Sande; 18.08.2020
comment
Я попытался использовать ваш пакет, и он выдал неточную ошибку массива типов. мой ввод также является фреймворком данных, есть идеи, как его решить? - person Xiaoxixi; 08.09.2020
comment
@Xiaoxixi, спасибо, что сообщили мне, у меня никогда не было проблем с этим. Каков тип каждого столбца? Вы можете проверить с помощью df.dtypes. Если у вас есть учетная запись github, можете ли вы создать проблему на странице github с небольшим фрагментом кода, который воспроизводит ошибку? Тогда я смогу быстро это исправить: github.com/Maarten-vd- Санде/qnorm/issues/new - person Maarten-vd-Sande; 08.09.2020
comment
@Xiaoxixi Я кое-что проверил, и это происходит, когда вы используете нестандартные типы данных (например, float16). Он по-прежнему будет падать, но теперь выводит сообщение о том, что вам нужно преобразовать, например, в. поплавок32 - person Maarten-vd-Sande; 16.09.2020
comment
@Maarten-vd-Sande, привет, извини, я пропустил твое первое сообщение. Но да, я попытался преобразовать его в числа с плавающей запятой, и это сработало! - person Xiaoxixi; 18.09.2020
comment
@Xiaoxixi Отлично! Новейшая версия должна выполнить преобразование автоматически для вас. - person Maarten-vd-Sande; 19.09.2020