Как вернуть количество знаков после запятой в R
Я работаю в R. У меня есть ряд координат в десятичных градусах, и я хотел бы отсортировать эти координаты по количеству десятичных разрядов, которые имеют эти числа (т.е. я хочу отбросить координаты, у которых слишком мало десятичных разрядов).
Есть ли функция в R, которая может возвращать количество десятичных разрядов, которые есть у числа, которые я мог бы включить в написание функции?
Пример ввода:
AniSom4 -17.23300000 -65.81700
AniSom5 -18.15000000 -63.86700
AniSom6 1.42444444 -75.86972
AniSom7 2.41700000 -76.81700
AniLac9 8.6000000 -71.15000
AniLac5 -0.4000000 -78.00000
В идеале я бы написал скрипт, который отбрасывал бы AniLac9 и AniLac 5, потому что эти координаты не были записаны с достаточной точностью. Я хотел бы отбросить координаты, для которых и долгота и широта имеют менее 3 ненулевых десятичных значений.
14 ответов
Вы можете легко написать небольшую функцию для задачи, например:
decimalplaces <- function(x) {
if ((x %% 1) != 0) {
nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed=TRUE)[[1]][[2]])
} else {
return(0)
}
}
И запустить:
> decimalplaces(23.43234525)
[1] 8
> decimalplaces(334.3410000000000000)
[1] 3
> decimalplaces(2.000)
[1] 0
Обновление (3 апреля 2018 г.) для адресации отчета @owen88 об ошибке из-за округления чисел с плавающей запятой двойной точности - замена x %% 1
проверять:
decimalplaces <- function(x) {
if (abs(x - round(x)) > .Machine$double.eps^0.5) {
nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed = TRUE)[[1]][[2]])
} else {
return(0)
}
}
Вот один из способов. Он проверяет первые 20 знаков после десятичной точки, но вы можете настроить число 20, если вы хотите что-то еще.
x <- pi
match(TRUE, round(x, 1:20) == x)
Вот другой способ.
nchar(strsplit(as.character(x), "\\.")[[1]][2])
Подходя к предложению Романа:
num.decimals <- function(x) {
stopifnot(class(x)=="numeric")
x <- sub("0+$","",x)
x <- sub("^.+[.]","",x)
nchar(x)
}
x <- "5.2300000"
num.decimals(x)
Если ваши данные не имеют надлежащей формы, вам следует больше проверять, чтобы другие символы не проникли внутрь.
Не уверен, почему этот простой подход не использовался выше (загрузите трубу из tidyverse/magrittr).
count_decimals = function(x) {
x_nchr = x %>% abs() %>% as.character() %>% nchar() %>% as.numeric()
x_int = floor(x) %>% abs() %>% nchar()
x_nchr = x_nchr - 1 - x_int
x_nchr[x_nchr < 0] = 0
x_nchr
}
> #test
> c(1, 1.1, 1.12, 1.123, 1.1234, 1.1, 1.10, 1.100, 1.1000) %>% count_decimals()
[1] 0 1 2 3 4 1 1 1 1
> c(1.1, 12.1, 123.1, 1234.1, 1234.12, 1234.123, 1234.1234) %>% count_decimals()
[1] 1 1 1 1 2 3 4
> seq(0, 1000, by = 100) %>% count_decimals()
[1] 0 0 0 0 0 0 0 0 0 0 0
> c(100.1234, -100.1234) %>% count_decimals()
[1] 4 4
Таким образом, R, кажется, не различает внутренне 1.000
а также 1
первоначально. Таким образом, если у каждого есть входной вектор различных десятичных чисел, можно увидеть, сколько цифр у него было изначально (по крайней мере), взяв максимальное значение числа десятичных знаков.
Отредактировано: исправлены ошибки
Если кому-то здесь нужна векторизованная версия функции, предоставленной Гергели Даронци выше:
decimalplaces <- function(x) {
ifelse(abs(x - round(x)) > .Machine$double.eps^0.5,
nchar(sub('^\\d+\\.', '', sub('0+$', '', as.character(x)))),
0)
}
decimalplaces(c(234.1, 3.7500, 1.345, 3e-15))
#> 1 2 3 0
as.character
использует научное обозначение для чисел, которые находятся между -1e-4 и 1e-4, но не равны нулю:
> as.character(0.0001)
[1] "1e-04"
Вы можете использоватьformat(scientific=F)
вместо:
> format(0.0001,scientific=F)
[1] "0.0001"
Затем сделайте следующее:
nchar(sub("^-?\\d*\\.?","",format(x,scientific=F)))
Или в векторной форме:
> nplaces=function(x)sapply(x,function(y)nchar(sub("^-?\\d*\\.?","",format(y,scientific=F))))
> nplaces(c(0,-1,1.1,0.123,1e-8,-1e-8))
[1] 0 0 1 3 8 8
Я протестировал некоторые решения и нашел, что это устойчиво к ошибкам, о которых сообщалось в других.
countDecimalPlaces <- function(x) {
if ((x %% 1) != 0) {
strs <- strsplit(as.character(format(x, scientific = F)), "\\.")
n <- nchar(strs[[1]][2])
} else {
n <- 0
}
return(n)
}
# example to prove the function with some values
xs <- c(1000.0, 100.0, 10.0, 1.0, 0, 0.1, 0.01, 0.001, 0.0001)
sapply(xs, FUN = countDecimalPlaces)
Опоздал на вечеринку, но вот мое векторизованное решение.
library(cpp11)
cpp_function('SEXP num_decimals(SEXP x, double tol){
int size = Rf_length(x);
double *p_x = REAL(x);
SEXP out = Rf_protect(Rf_allocVector(INTSXP, size));
int *p_out = INTEGER(out);
for (int i = 0; i < size; ++i){
int n = 0;
double y = p_x[i];
while (std::fabs(y - std::round(y)) >= tol){
y = y * 10.0;
++n;
}
p_out[i] = n;
}
Rf_unprotect(1);
return out;
}')
tol <- sqrt(.Machine$double.eps) * 10
num_decimals(0, tol)
#> [1] 0
num_decimals(1.123, tol)
#> [1] 3
num_decimals(c(0, 1, 1.123, 1.12345678, pi), tol)
#> [1] 0 0 3 8 15
Создано 20 ноября 2023 г. с использованием reprex v2.0.2.
Не хочу перехватывать поток, просто разместите его здесь, так как это может помочь кому-то справиться с задачей, которую я пытался выполнить с помощью предложенного кода.
К сожалению, даже обновленное решение @daroczig не помогло мне проверить, есть ли число меньше 8 десятичных цифр.
Код @daroczig:
decimalplaces <- function(x) {
if (abs(x - round(x)) > .Machine$double.eps^0.5) {
nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed = TRUE)[[1]][[2]])
} else {
return(0)
}
}
В моем случае получены следующие результаты
NUMBER / NUMBER OF DECIMAL DIGITS AS PRODUCED BY THE CODE ABOVE
[1] "0.0000437 7"
[1] "0.000195 6"
[1] "0.00025 20"
[1] "0.000193 6"
[1] "0.000115 6"
[1] "0.00012501 8"
[1] "0.00012701 20"
и т.п.
До сих пор удалось выполнить необходимые тесты с помощью следующего неуклюжего кода:
if (abs(x*10^8 - floor(as.numeric(as.character(x*10^8)))) > .Machine$double.eps*10^8)
{
print("The number has more than 8 decimal digits")
}
PS: я мог бы что-то упустить в связи с тем, чтобы не получить корень .Machine$double.eps
поэтому, пожалуйста, будьте осторожны
В [R] нет разницы между 2.30000 и 2.3, оба округляются до 2.3, поэтому один не является более точным, чем другой, если вы хотите это проверить. С другой стороны, если это не то, что вы имели в виду: если вы действительно хотите это сделать, вы можете использовать 1) умножить на 10, 2) использовать функцию floor() 3) разделить на 10 4) проверить на равенство с оригиналом. (Однако учтите, что сравнение поплавков на равенство - плохая практика, убедитесь, что это именно то, что вам нужно)
Интересный вопрос. Вот еще один твик работы вышеупомянутых респондентов, векторизованный и расширенный для обработки цифр слева от десятичной точки. Проверено на отрицательные цифры, которые дали бы неверный результат для предыдущего strsplit()
подход.
Если желательно считать только те, что справа, trailingonly
аргумент может быть установлен в TRUE
,
nd1 <- function(xx,places=15,trailingonly=F) {
xx<-abs(xx);
if(length(xx)>1) {
fn<-sys.function();
return(sapply(xx,fn,places=places,trailingonly=trailingonly))};
if(xx %in% 0:9) return(!trailingonly+0);
mtch0<-round(xx,nds <- 0:places);
out <- nds[match(TRUE,mtch0==xx)];
if(trailingonly) return(out);
mtch1 <- floor(xx*10^-nds);
out + nds[match(TRUE,mtch1==0)]
}
Здесь strsplit()
версия.
nd2 <- function(xx,trailingonly=F,...) if(length(xx)>1) {
fn<-sys.function();
return(sapply(xx,fn,trailingonly=trailingonly))
} else {
sum(c(nchar(strsplit(as.character(abs(xx)),'\\.')[[1]][ifelse(trailingonly, 2, T)]),0),na.rm=T);
}
Строковая версия обрезается до 15 цифр (на самом деле, не уверен, почему аргумент места другого отключен на одну единицу... причина, по которой он превышен, заключается в том, что он считает цифры в обоих направлениях, чтобы он мог увеличиться вдвое, если номер достаточно большой). Вероятно, есть некоторая опция форматирования as.character()
что может дать nd2()
эквивалентный вариант для places
аргумент nd1()
,
nd1(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0));
# 2 2 1 3 1 4 16 17 1
nd2(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0));
# 2 2 1 3 1 4 15 15 1
nd1()
быстрее.
rowSums(replicate(10,system.time(replicate(100,nd1(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0))))));
rowSums(replicate(10,system.time(replicate(100,nd2(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0))))));
Для обычного применения приведем модификацию кода Дароцига для обработки векторов:
decimalplaces <- function(x) {
y = x[!is.na(x)]
if (length(y) == 0) {
return(0)
}
if (any((y %% 1) != 0)) {
info = strsplit(sub('0+$', '', as.character(y)), ".", fixed=TRUE)
info = info[sapply(info, FUN=length) == 2]
dec = nchar(unlist(info))[seq(2, length(info), 2)]
return(max(dec, na.rm=T))
} else {
return(0)
}
}
В общем, могут возникнуть проблемы с тем, как число с плавающей запятой хранится в двоичном виде. Попробуй это:
> sprintf("%1.128f", 0.00000000001)
[1] "0.00000000000999999999999999939458150688409432405023835599422454833984375000000000000000000000000000000000000000000000000000000000"
Сколько десятичных знаков у нас сейчас?
Другой вклад, сохраняя полностью в виде числовых представлений без преобразования в символ:
countdecimals <- function(x)
{
n <- 0
while (!isTRUE(all.equal(floor(x),x)) & n <= 1e6) { x <- x*10; n <- n+1 }
return (n)
}
Векторное решение на основе функции Дароцига (также может работать с грязными столбцами, содержащими строки и цифры):
decimalplaces_vec <- function(x) {
vector <- c()
for (i in 1:length(x)){
if(!is.na(as.numeric(x[i]))){
if ((as.numeric(x[i]) %% 1) != 0) {
vector <- c(vector, nchar(strsplit(sub('0+$', '', as.character(x[i])), ".", fixed=TRUE)[[1]][[2]]))
}else{
vector <- c(vector, 0)
}
}else{
vector <- c(vector, NA)
}
}
return(max(vector))
}