Skip to content

[SPARK-23291][SQL][R] R's substr should not reduce starting position by 1 when calling Scala API#20464

Closed
viirya wants to merge 5 commits into
apache:masterfrom
viirya:SPARK-23291
Closed

[SPARK-23291][SQL][R] R's substr should not reduce starting position by 1 when calling Scala API#20464
viirya wants to merge 5 commits into
apache:masterfrom
viirya:SPARK-23291

Conversation

@viirya

@viirya viirya commented Feb 1, 2018

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

Seems R's substr API treats Scala substr API as zero based and so subtracts the given starting position by 1.

Because Scala's substr API also accepts zero-based starting position (treated as the first element), so the current R's substr test results are correct as they all use 1 as starting positions.

How was this patch tested?

Modified tests.

@viirya

viirya commented Feb 1, 2018

Copy link
Copy Markdown
Member Author

One more thing to notice is that the two parameters (starting and ending positions) of R's substr API is also unaligned with Scala's substr which takes starting position and substring length.

@viirya

viirya commented Feb 1, 2018

Copy link
Copy Markdown
Member Author

cc @felixcheung @HyukjinKwon

@srowen

srowen commented Feb 1, 2018

Copy link
Copy Markdown
Member

Also @shivaram

@shivaram

shivaram commented Feb 1, 2018

Copy link
Copy Markdown
Contributor

One thing to keep in mind is what the user's perception of the API is. If R users are going to use 1-based indexing then this might not be the right fix ? http://stat.ethz.ch/R-manual/R-devel/library/base/html/substr.html is the base R function FWIW

@viirya

viirya commented Feb 1, 2018

Copy link
Copy Markdown
Member Author

@shivaram This fix is to make it correctly 1-based. Previously SparkR substr API substracts starting position by 1, so it becomes zero-based.

This fix matches R's substr in above link as I test:

> substr("Michael", 4, 6)                                                                                                                  
[1] "hae"

Before this fix, SparkR's substr returns "cha".

@shivaram

shivaram commented Feb 1, 2018

Copy link
Copy Markdown
Contributor

Thanks for clarifying @viirya. Is the PR description accurate ? I read it as ..SQL's substr also accepts zero-based starting position while R uses a 1-based starting position.

@SparkQA

SparkQA commented Feb 1, 2018

Copy link
Copy Markdown

Test build #86908 has finished for PR 20464 at commit a2ffdc1.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@viirya

viirya commented Feb 1, 2018

Copy link
Copy Markdown
Member Author

@shivaram Thanks for pointing out it. I made change to the description. Hopefully it is clearer now. Basically I just want to clarify why R's substr tests are correct previously.

@HyukjinKwon

Copy link
Copy Markdown
Member

I was just manually double checking both substr in R and this. It seems correct; however, I think we should add a note in the doc and release note ...

One followup question is though, would it be difficult to match the behaviour with substr in R when the index is 0 or minus? If i understood #20464 (comment) correctly, it sounds better to match it to substr's behaviour in R. Took a quick look/test and seems we can just set start to 1 for both cases.

If this followup question is something we are not sure yet, I think we might be okay as is.

@HyukjinKwon

Copy link
Copy Markdown
Member

Just in case, I am testing with:

df <- createDataFrame(list(list(a="abcdef")))
collect(select(df, substr(df$a, 4, 5)))
substr("abcdef", 4, 5)

just in case it helps to check and reproduce.

@viirya

viirya commented Feb 1, 2018

Copy link
Copy Markdown
Member Author

One followup question is though, would it be difficult to match the behaviour with substr in R when the index is 0 or minus? If i understood #20464 (comment) correctly, it sounds better to match it to substr's behaviour in R. Took a quick look/test and seems we can just set start to 1 for both cases.

If we both consider the indices at starting and ending, setting them to 1 seems not enough. E.g.,

> substr("abcdef", -2, -3)
[1] ""
> substr("abcdef", 1, 1)
[1] "a"

For the cases when only ending is zero/negative, no matter what starting is, the result is empty string.

For the cases when only starting is zero/negative, we can set it to 1.

For the cases they are both zero/negative, the result is empty string.

We can address this in another PR.

Comment thread R/pkg/R/column.R
setMethod("substr", signature(x = "Column"),
function(x, start, stop) {
jc <- callJMethod(x@jc, "substr", as.integer(start - 1), as.integer(stop - start + 1))
jc <- callJMethod(x@jc, "substr", as.integer(start), as.integer(stop - start + 1))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concern with changing this. As you can see it's been like this from the very beginning...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API behavior should be considered as wrong and performs inconsistently. Because for starting position 1, we get substring from 1st element, but for position 2, we still get the substring from 1. So we will get the following inconsistent results:

> collect(select(df, substr(df$a, 1, 5)))
  substring(a, 0, 5)
1              abcde
> collect(select(df, substr(df$a, 2, 5)))
  substring(a, 1, 4)
1               abcd

For such change, we might need to add a note in the doc as @HyukjinKwon suggested.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because I think it is another issue regarding 0/negative indices. I can deal it here if you strongly feel it is better.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way to make the behavior the same before this change for any caller calling substr with common index like 0

Should we keep the behavior when calling substr with 0 as start index?

> df <- createDataFrame(list(list(a="abcdef")))
> collect(select(df, substr(df$a, 0, 5)))
  substring(a, -1, 6)
1                   f
> substr("abcdef", 0, 5)
[1] "abcde"

I think the previous behavior is pretty unreasonable..

@SparkQA

SparkQA commented Feb 1, 2018

Copy link
Copy Markdown

Test build #86924 has finished for PR 20464 at commit 95c8a4e.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

Comment thread docs/sparkr.md Outdated

## Upgrading to Spark 2.4.0

- The first parameter of `substr` method was wrongly subtracted by one, previously. This can lead to inconsistent substring results and also does not match with the behaviour with `substr` in R. It has been corrected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of The first parameter of -> The ``start`` parameter of...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment thread R/pkg/R/column.R
setMethod("substr", signature(x = "Column"),
function(x, start, stop) {
jc <- callJMethod(x@jc, "substr", as.integer(start - 1), as.integer(stop - start + 1))
jc <- callJMethod(x@jc, "substr", as.integer(start), as.integer(stop - start + 1))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question:

@SparkQA

SparkQA commented Feb 2, 2018

Copy link
Copy Markdown

Test build #86985 has finished for PR 20464 at commit d994d76.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@HyukjinKwon HyukjinKwon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I admit it's correct .. so LGTM but let me leave it to @felixcheung and @shivaram ..

@shivaram

shivaram commented Feb 7, 2018

Copy link
Copy Markdown
Contributor

I think @felixcheung has the most context here, so I'd suggest we wait for his comments.

@viirya

viirya commented Feb 15, 2018

Copy link
Copy Markdown
Member Author

ping @felixcheung

@felixcheung

Copy link
Copy Markdown
Member

Sorry, I'm a bit occupied with testing 2.3 RC, will get back to this after.

@viirya

viirya commented Feb 16, 2018

Copy link
Copy Markdown
Member Author

@felixcheung Thanks!

@viirya

viirya commented Mar 2, 2018

Copy link
Copy Markdown
Member Author

Because 2.3 is released, ping @felixcheung again

Comment thread R/pkg/R/column.R
setMethod("substr", signature(x = "Column"),
function(x, start, stop) {
jc <- callJMethod(x@jc, "substr", as.integer(start - 1), as.integer(stop - start + 1))
jc <- callJMethod(x@jc, "substr", as.integer(start), as.integer(stop - start + 1))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should do two things:

  1. add to the func doc that the start param should be 0-base and to add to the example with the result
    collect(select(df, substr(df$a, 0, 5))) # this should give you...

@viirya viirya Mar 6, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean 1-base.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to the func doc.

Comment thread docs/sparkr.md Outdated

## Upgrading to Spark 2.4.0

- The `start` parameter of `substr` method was wrongly subtracted by one, previously. This can lead to inconsistent substring results and also does not match with the behaviour with `substr` in R. It has been corrected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. in the migration guide we should give a concrete example with non-0 start index, eg.
    substr(df$a, 1, 6) should be changed to substr(df$a, 0, 5)

@SparkQA

SparkQA commented Mar 6, 2018

Copy link
Copy Markdown

Test build #87993 has finished for PR 20464 at commit 0ebdf74.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@felixcheung

Copy link
Copy Markdown
Member

appveyor tests failed, could you close and reopen this PR to trigger it.

strange, I haven't seen anything like this on appveyor a long time.

1. Error: create DataFrame with complex types (@test_sparkSQL.R#535) -----------
8712org.apache.spark.SparkException: Job aborted due to stage failure: Task 0 in stage 114.0 failed 1 times, most recent failure: Lost task 0.0 in stage 114.0 (TID 116, localhost, executor driver): java.net.SocketTimeoutException: Accept timed out

@viirya viirya closed this Mar 6, 2018
@viirya viirya reopened this Mar 6, 2018

@felixcheung felixcheung left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG, pending tests.
one small comment for clarity. thanks!

Comment thread docs/sparkr.md Outdated

## Upgrading to Spark 2.4.0

- The `start` parameter of `substr` method was wrongly subtracted by one, previously. In other words, the index specified by `start` parameter was considered as 0-base. This can lead to inconsistent substring results and also does not match with the behaviour with `substr` in R. It has been fixed so the `start` parameter of `substr` method is now 1-base, e.g., `substr(df$a, 2, 5)` should be changed to `substr(df$a, 1, 4)`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add
method is now 1-base, e.g., therefore to get the same result as substr(df$a, 2, 5), it should be changed to substr(df$a, 1, 4)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Added.

@SparkQA

SparkQA commented Mar 7, 2018

Copy link
Copy Markdown

Test build #88039 has finished for PR 20464 at commit 8c1a8ec.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@felixcheung

Copy link
Copy Markdown
Member

merged to master, thanks!

@asfgit asfgit closed this in 53561d2 Mar 7, 2018
HyukjinKwon pushed a commit to HyukjinKwon/spark that referenced this pull request May 6, 2018
…by 1 when calling Scala API

## What changes were proposed in this pull request?

Seems R's substr API treats Scala substr API as zero based and so subtracts the given starting position by 1.

Because Scala's substr API also accepts zero-based starting position (treated as the first element), so the current R's substr test results are correct as they all use 1 as starting positions.

## How was this patch tested?

Modified tests.

Author: Liang-Chi Hsieh <viirya@gmail.com>

Closes apache#20464 from viirya/SPARK-23291.
@viirya viirya deleted the SPARK-23291 branch December 27, 2023 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants