Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pandas-vet] autofixes for PD003, PD004, PD008, PD009 and PD011 #2741

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1322,13 +1322,13 @@ For more, see [pandas-vet](https://pypi.org/project/pandas-vet/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PD002 | use-of-inplace-argument | `inplace=True` should be avoided; it has inconsistent behavior | 🛠 |
| PD003 | use-of-dot-is-null | `.isna` is preferred to `.isnull`; functionality is equivalent | |
| PD004 | use-of-dot-not-null | `.notna` is preferred to `.notnull`; functionality is equivalent | |
| PD003 | use-of-dot-is-null | `.isna` is preferred to `.isnull`; functionality is equivalent | 🛠 |
| PD004 | use-of-dot-not-null | `.notna` is preferred to `.notnull`; functionality is equivalent | 🛠 |
| PD007 | use-of-dot-ix | `.ix` is deprecated; use more explicit `.loc` or `.iloc` | |
| PD008 | use-of-dot-at | Use `.loc` instead of `.at`. If speed is important, use numpy. | |
| PD009 | use-of-dot-iat | Use `.iloc` instead of `.iat`. If speed is important, use numpy. | |
| PD008 | use-of-dot-at | Use `.loc` instead of `.at`. If speed is important, use numpy. | 🛠 |
| PD009 | use-of-dot-iat | Use `.iloc` instead of `.iat`. If speed is important, use numpy. | 🛠 |
| PD010 | use-of-dot-pivot-or-unstack | `.pivot_table` is preferred to `.pivot` or `.unstack`; provides same functionality | |
| PD011 | use-of-dot-values | Use `.to_numpy()` instead of `.values` | |
| PD011 | use-of-dot-values | Use `.to_numpy()` instead of `.values` | 🛠 |
| PD012 | use-of-dot-read-table | `.read_csv` is preferred to `.read_table`; provides same functionality | |
| PD013 | use-of-dot-stack | `.melt` is preferred to `.stack`; provides same functionality | |
| PD015 | use-of-pd-merge | Use `.merge` method instead of `pd.merge` function. They have equivalent functionality. | |
Expand Down
14 changes: 14 additions & 0 deletions crates/ruff/resources/test/fixtures/pandas_vet/fixes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pandas as pd

df = pd.DataFrame()

df.isnull()
df.isna()
df.notnull()
df.notna()
df.at[1]
df.loc[1]
df.iat[1]
df.iloc[1]
df.values
df.to_numpy()
18 changes: 16 additions & 2 deletions crates/ruff/src/rules/pandas_vet/fixes.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use rustpython_parser::ast::{Expr, ExprKind, Keyword, Location};
use rustpython_parser::ast::{Expr, ExprContext, ExprKind, Keyword, Location};

use crate::ast::helpers;
use crate::ast::types::Range;
use crate::autofix::apply_fix;
use crate::autofix::helpers::remove_argument;
use crate::fix::Fix;
use crate::source_code::Locator;
use crate::source_code::{Locator, Stylist};

fn match_name(expr: &Expr) -> Option<&str> {
if let ExprKind::Call { func, .. } = &expr.node {
Expand Down Expand Up @@ -70,3 +70,17 @@ pub fn fix_inplace_argument(
None
}
}

/// Replace attribute with `attr`
pub fn fix_attr(attr: &str, value: &Expr, stylist: &Stylist) -> String {
let stmt = Expr::new(
Location::default(),
Location::default(),
ExprKind::Attribute {
value: Box::new(value.clone()),
attr: attr.to_string(),
ctx: ExprContext::Store,
},
);
helpers::unparse_expr(&stmt, stylist)
}
5 changes: 5 additions & 0 deletions crates/ruff/src/rules/pandas_vet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ mod tests {
}

#[test_case(Rule::UseOfInplaceArgument, Path::new("PD002.py"); "PD002")]
#[test_case(Rule::UseOfDotIsNull, Path::new("fixes.py"); "PD003")]
#[test_case(Rule::UseOfDotNotNull, Path::new("fixes.py"); "PD004")]
#[test_case(Rule::UseOfDotAt, Path::new("fixes.py"); "PD008")]
#[test_case(Rule::UseOfDotIat, Path::new("fixes.py"); "PD009")]
#[test_case(Rule::UseOfDotValues, Path::new("fixes.py"); "PD011")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
let diagnostics = test_path(
Expand Down
43 changes: 36 additions & 7 deletions crates/ruff/src/rules/pandas_vet/rules/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use rustpython_parser::ast::{ExprKind, Located};

use crate::ast::types::{BindingKind, Range};
use crate::checkers::ast::Checker;
use crate::fix::Fix;
use crate::registry::{Diagnostic, DiagnosticKind, Rule};
use crate::rules::pandas_vet::fixes::fix_attr;
use crate::rules::pandas_vet::helpers::is_dataframe_candidate;
use crate::violation::Violation;
use crate::violation::{AlwaysAutofixableViolation, Violation};

define_violation!(
pub struct UseOfDotIx;
Expand All @@ -20,31 +22,43 @@ impl Violation for UseOfDotIx {
define_violation!(
pub struct UseOfDotAt;
);
impl Violation for UseOfDotAt {
impl AlwaysAutofixableViolation for UseOfDotAt {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `.loc` instead of `.at`. If speed is important, use numpy.")
}

fn autofix_title(&self) -> String {
format!("Replace `.at` with `.loc`")
}
}

define_violation!(
pub struct UseOfDotIat;
);
impl Violation for UseOfDotIat {
impl AlwaysAutofixableViolation for UseOfDotIat {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `.iloc` instead of `.iat`. If speed is important, use numpy.")
}

fn autofix_title(&self) -> String {
format!("Replace `.iat` with `.iloc`")
}
}

define_violation!(
pub struct UseOfDotValues;
);
impl Violation for UseOfDotValues {
impl AlwaysAutofixableViolation for UseOfDotValues {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `.to_numpy()` instead of `.values`")
}

fn autofix_title(&self) -> String {
format!("Replace `.values` with `.to_numpy()`")
}
}

pub fn check_attr(
Expand Down Expand Up @@ -94,7 +108,22 @@ pub fn check_attr(
}
}

checker
.diagnostics
.push(Diagnostic::new(violation, Range::from_located(attr_expr)));
let mut diagnostic = Diagnostic::new(violation, Range::from_located(attr_expr));
if checker.patch(diagnostic.kind.rule()) {
let replacement = match *diagnostic.kind.rule() {
Rule::UseOfDotAt => Some("loc"),
Rule::UseOfDotIat => Some("iloc"),
Rule::UseOfDotValues => Some("to_numpy()"),
_ => None,
};
if let Some(replacement) = replacement {
diagnostic.amend(Fix::replacement(
fix_attr(replacement, value, checker.stylist),
attr_expr.location,
attr_expr.end_location.unwrap(),
));
}
}

checker.diagnostics.push(diagnostic);
}
36 changes: 30 additions & 6 deletions crates/ruff/src/rules/pandas_vet/rules/check_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,38 @@ use rustpython_parser::ast::{ExprKind, Located};

use crate::ast::types::{BindingKind, Range};
use crate::checkers::ast::Checker;
use crate::fix::Fix;
use crate::registry::{Diagnostic, DiagnosticKind, Rule};
use crate::rules::pandas_vet::fixes::fix_attr;
use crate::rules::pandas_vet::helpers::is_dataframe_candidate;
use crate::violation::Violation;
use crate::violation::{AlwaysAutofixableViolation, Violation};

define_violation!(
pub struct UseOfDotIsNull;
);
impl Violation for UseOfDotIsNull {
impl AlwaysAutofixableViolation for UseOfDotIsNull {
#[derive_message_formats]
fn message(&self) -> String {
format!("`.isna` is preferred to `.isnull`; functionality is equivalent")
}

fn autofix_title(&self) -> String {
format!("Replace `.isnull` with `.isna`")
}
}

define_violation!(
pub struct UseOfDotNotNull;
);
impl Violation for UseOfDotNotNull {
impl AlwaysAutofixableViolation for UseOfDotNotNull {
#[derive_message_formats]
fn message(&self) -> String {
format!("`.notna` is preferred to `.notnull`; functionality is equivalent")
}

fn autofix_title(&self) -> String {
format!("Replace `.notnull` with `.notna`")
}
}

define_violation!(
Expand Down Expand Up @@ -102,7 +112,21 @@ pub fn check_call(checker: &mut Checker, func: &Located<ExprKind>) {
}
}

checker
.diagnostics
.push(Diagnostic::new(violation, Range::from_located(func)));
let mut diagnostic = Diagnostic::new(violation, Range::from_located(func));
if checker.patch(diagnostic.kind.rule()) {
let replacement = match *diagnostic.kind.rule() {
Rule::UseOfDotIsNull => Some("isna"),
Rule::UseOfDotNotNull => Some("notna"),
_ => None,
};
if let Some(replacement) = replacement {
diagnostic.amend(Fix::replacement(
fix_attr(replacement, value, checker.stylist),
func.location,
func.end_location.unwrap(),
));
}
}

checker.diagnostics.push(diagnostic);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotIsNull: ~
location:
row: 5
column: 0
end_location:
row: 5
column: 9
fix:
content:
- df.isna
location:
row: 5
column: 0
end_location:
row: 5
column: 9
parent: ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotNotNull: ~
location:
row: 7
column: 0
end_location:
row: 7
column: 10
fix:
content:
- df.notna
location:
row: 7
column: 0
end_location:
row: 7
column: 10
parent: ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotAt: ~
location:
row: 9
column: 0
end_location:
row: 9
column: 5
fix:
content:
- df.loc
location:
row: 9
column: 0
end_location:
row: 9
column: 5
parent: ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotIat: ~
location:
row: 11
column: 0
end_location:
row: 11
column: 6
fix:
content:
- df.iloc
location:
row: 11
column: 0
end_location:
row: 11
column: 6
parent: ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: crates/ruff/src/rules/pandas_vet/mod.rs
expression: diagnostics
---
- kind:
UseOfDotValues: ~
location:
row: 13
column: 0
end_location:
row: 13
column: 9
fix:
content:
- df.to_numpy()
location:
row: 13
column: 0
end_location:
row: 13
column: 9
parent: ~