在保留减号的同时删除某些列中的数值?


9

我有以下数据框,该数据框仅在奇数列中以负数无限地水平和垂直地继续:

-1  2  3  4 -5  9
 2  3 -4  5 -6  11

我想要第二,第四和第六完整列(或每个偶数列),而负号仅来自第一,第三和第五(或每个奇数列),所以我得到了:

- 2   4 - 9
  3 - 5 - 11

最终结果如下:

-2  4 -9
 3 -5 -11

因此,我需要不改变偶数列和奇数列的值,如果有一个负值,则保留-仅值,如果有一个正值,则将其丢弃。

有没有办法用awk / sed做到这一点?

据我所知,这是差不多的了:

awk '{ for (i=2;i<=NF;i+=2) $i="" }1' FILE.txt | sed 's/[0-9,.]*//g' 

当您说数据帧无限期连续时,您是指水平还是垂直?您实际上有几列?
terdon

都。我的测试数据是3行3列,但实际数据有不同的数字,我想说40行40列。
2015年

Answers:


2

这是一种方法:

$ awk '{for(i=1;i<=NF;i+=2){if($i<0){$i="-"}else{$i="";} }};1' file |
     sed 's/- */-/g; s/  */ /g'
-2 4 -9
 3 -5 -11

awk脚本遍历所有奇数列,并将它们的值设置-为负(如果为负),否则为空。然后,sed删除a -之后的所有空格,然后用单个空格替换多个连续的空格。请注意,这意味着对齐将被破坏,因为某些字段将具有两个或更多字符,而其他字段将具有一个或多个字符。如果您正在使用字段,那将不会成为问题,因为它们看起来并不漂亮。


4

sed方式:

sed -E '
    s/^(([ \t]*-?[ \t]*[0-9.]+[ \t]+[0-9.]+)*)[ \t]+-?[ \t]*[0-9.]+$/\1/;
    s/[0-9.]+[ \t]+([0-9.]+)/\1/g'

输出:

-2  4 -9
 3 -5 -11

如果列数为奇数,则第一个表达式将终止尾随的列。它通过查找0对或更多对来实现<number> <number>,其中第一个数字可以为负。

编辑:一个较短的sed解决方案,受到@mikeserv的启发:

sed -E '
    s/[0-9.]+[ \t]*([0-9.]*)/\1/g;
    s/[- \t]*$//'

perl

perl -lpe 's/^((\s*-?\s*[\d.]+\s*[\d.]+)*)\s+-?\s*[\d.]+$/$1/o; s/[\d.]+\s+([\d.]+)/$1/g'

另一种方法perl(可能是最干净的方法):

perl -lpe '$a = 1; s/([\d.]+\s*)/$a++ % 2 ? "" : $1/eg; s/[-\s]*$//o'

只要将小数点添加到脚本中,这对我的实际数据就可以正常工作。谢谢!
2015年

@Asfound好的,我编辑了答案以也支持小数点。
lcd047

等等,如果最后一个(奇数)字段为负值,则将失败。
terdon

@terdon如果列数为奇数,则失败,是的。但是要么有6列,要么是“无限多”,而“无限多”不是奇数。:)
lcd047

OP ,最多可以有“ 40列” :(
terdon

3

一个perl一个:

$ perl -anle 'BEGIN{$,=" "}
  print map{$_=$F[$_]=~/^-/?"-$F[$_+1]":" $F[$_+1]"}grep{!($_%2)}0..$#F' file
-2  4 -9
 3 -5 -11
  • -an将输入拆分为@F数组
  • BEGIN{$,=" "} 将输出字段分隔符设置为空格
  • grep{!($_%2)}0..$#F获取@F数组中的所有偶数索引,它们是奇数元素的索引
  • map{$_=$F[$_]=~/^-/?"-$F[$_+1]":" $F[$_+1]"}检查奇数元素是否以开头-,然后追加-到下一个偶数元素,否则追加空格

3

作为@terdon的答案,但没有sed:

awk '{ for(i=1;i<=NF;i+=2){
         if ($i<0) $(i+1)*=-1;
         $i = "";
       }
       print
     }'

3

一个python解决方案

python -c 'from __future__ import print_function; 
import sys, math;
for line in sys.stdin:
  x = [int(y) for y in line.split()]
  print(*[int(math.copysign(b, a)) for a, b in zip(x[::2], x[1::2])], sep=" ")
' <file

2

一个简单的基于数学的awk解决方案:

$ cat <<M | awk '{for(i=2;i<=NF;i+=2){printf "%4s",($(i-1)<0?-1:1)*$i}print ""}'
-1  2  3  4 -5  9
2  3.2 -4  5 -6
M

  -2   4  -9
 3.2  -5
  • 从第二个(i=2)循环到最后一个字段(i<=NF)。
  • 将上一个字段($(i-1))乘以-1或1。
  • 很好地格式化输出(printf "%4s"),并打印结尾的换行符(print "")。

唯一需要注意的是,如果列数为奇数,则最后一个字段将根本不显示任何内容。希望这就是您的期望。显然,这就是您所期望的。:)

(已编辑以使用十进制值,并使循环条件与问题更一致,同时保存2个字符。)


1

您需要完全忘记负面因素-忽略它。您要合并两个字段-从左到右。那很容易。

sed '   s/ *\(.*\)/\1 /
        s/\([0-9]*  *\)\{2\}/\1/g
        s/[ -]*$//
' <<\IN
-1  2  3  4 -5  9
 2  3 -4  5 -6  11
IN
-2  4 -9
3 -5 -11

请注意,我完全避免了对符号的引用-处理输入时,自动机将仅接受空格或数字,因为它对其他内容一无所知-所有其他内容将被完全忽略并保留在原位。

当您为子表达式指定\{数字重复间隔时\},将仅向后引用该表达式的最后一次出现。因此,您可以轻松挤压或截断一个重复间隔。而且由于我们将重复符号挤压在符号后面(如果有的话),则该模式的第二次出现将跟在第一个出现之前的所有符号之后。\(\)\1

POSIX为所有符合BRE的应用程序指定了上述行为,但是很少sed有正确的行为。GNU sed可以。

最后,空格仅使模式发生规则

当然,这永远不会为您服务。或者,也许更正确地讲,它将始终为您工作,但永远不会返回任何结果。如果模式不确定,怎么办?


仅当字段数偶数时才起作用。
terdon

@terdon-不-它适用于任何情况。
mikeserv

不,尝试使用奇数个字段。最后一个已打印,不应打印。
terdon

@terdon-为什么不呢?没有以下字段可以将其取消?询问者状态他们想要删除奇数列,后跟偶数列。最后一列后没有偶数列-它完全按照应有的方式工作,并且删除得尽可能少。在我看来,假设应该删除一些数据是不好的做法。
mikeserv

不,它们不是:“因此,我需要偶数列和奇数列的值保持不变,如果存在负值,则仅保留-,如果存在正值,则将其丢弃。” 绝不要打印奇数字段,它们应该传递的唯一信息是它们是否为负数。您的打印正的奇数字段。
terdon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.